@tmagic/editor 1.8.0-beta.2 → 1.8.0-beta.21

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 (308) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +43 -9
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +8 -4
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +3 -2
  4. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +22 -98
  5. package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +32 -9
  6. package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +68 -17
  7. package/dist/es/components/Icon.vue_vue_type_script_setup_true_lang.js +1 -1
  8. package/dist/es/components/Resizer.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/es/components/ScrollBar.js +0 -1
  10. package/dist/es/components/ScrollBar.vue_vue_type_script_setup_true_lang.js +1 -1
  11. package/dist/es/components/ScrollViewer.vue_vue_type_script_setup_true_lang.js +1 -1
  12. package/dist/es/components/SearchInput.vue_vue_type_script_setup_true_lang.js +1 -1
  13. package/dist/es/components/SplitView.vue_vue_type_script_setup_true_lang.js +1 -1
  14. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +7 -7
  15. package/dist/es/components/Tree.vue_vue_type_script_setup_true_lang.js +1 -1
  16. package/dist/es/components/TreeNode.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/es/components/ViewForm.js +5 -0
  18. package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +58 -0
  19. package/dist/es/editorProps.js +9 -1
  20. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +16 -5
  21. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +7 -7
  22. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +81 -44
  23. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +10 -3
  24. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +7 -19
  25. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +13 -6
  26. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +11 -8
  27. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +39 -12
  28. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +11 -20
  29. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +11 -6
  30. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +30 -14
  31. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +11 -4
  32. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +5 -2
  33. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +38 -6
  34. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +156 -155
  35. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +5 -2
  36. package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +5 -2
  37. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +8 -10
  38. package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +5 -2
  39. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +28 -6
  40. package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +1 -1
  41. package/dist/es/fields/StyleSetter/icons/align-items/Center.js +31 -0
  42. package/dist/es/fields/StyleSetter/icons/align-items/FlexEnd.js +31 -0
  43. package/dist/es/fields/StyleSetter/icons/align-items/FlexStart.js +31 -0
  44. package/dist/es/fields/StyleSetter/icons/align-items/SpaceAround.js +50 -0
  45. package/dist/es/fields/StyleSetter/icons/align-items/SpaceBetween.js +50 -0
  46. package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +7 -7
  47. package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +6 -3
  48. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +29 -6
  49. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +32 -6
  50. package/dist/es/fields/StyleSetter/icons/display/Block.js +1 -1
  51. package/dist/es/fields/StyleSetter/icons/display/Flex.js +1 -1
  52. package/dist/es/fields/StyleSetter/icons/display/Inline.js +1 -1
  53. package/dist/es/fields/StyleSetter/icons/display/InlineBlock.js +1 -1
  54. package/dist/es/fields/StyleSetter/icons/display/None.js +1 -1
  55. package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +11 -3
  56. package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +11 -3
  57. package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +11 -3
  58. package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +11 -3
  59. package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +17 -3
  60. package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +19 -3
  61. package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +19 -3
  62. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +37 -3
  63. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +33 -3
  64. package/dist/es/fields/StyleSetter/icons/text-align/Center.js +1 -1
  65. package/dist/es/fields/StyleSetter/icons/text-align/Left.js +1 -1
  66. package/dist/es/fields/StyleSetter/icons/text-align/Right.js +1 -1
  67. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +56 -30
  68. package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +7 -4
  69. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +35 -26
  70. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +76 -48
  71. package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +29 -23
  72. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +7 -4
  73. package/dist/es/fields/UISelect.js +0 -1
  74. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +5 -2
  75. package/dist/es/hooks/use-code-block-edit.js +6 -3
  76. package/dist/es/hooks/use-compare-form.js +119 -0
  77. package/dist/es/hooks/use-data-source-edit.js +5 -2
  78. package/dist/es/hooks/use-stage.js +9 -4
  79. package/dist/es/icons/CenterIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  80. package/dist/es/icons/CodeIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  81. package/dist/es/icons/DatasourceIcon.js +17 -0
  82. package/dist/es/icons/FolderMinusIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  83. package/dist/es/index.js +24 -10
  84. package/dist/es/initService.js +19 -13
  85. package/dist/es/layouts/AddPageBox.vue_vue_type_script_setup_true_lang.js +1 -1
  86. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +24 -12
  87. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +25 -18
  88. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +4 -4
  89. package/dist/es/layouts/NavMenuColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  90. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +25 -38
  91. package/dist/es/layouts/history-list/BucketTab.js +5 -0
  92. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +62 -0
  93. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +177 -88
  94. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +139 -37
  95. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +164 -107
  96. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +44 -9
  97. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +49 -53
  98. package/dist/es/layouts/history-list/composables.js +138 -108
  99. package/dist/es/layouts/history-list/useHistoryList.js +56 -0
  100. package/dist/es/layouts/history-list/useHistoryRevert.js +317 -0
  101. package/dist/es/layouts/page-bar/AddButton.vue_vue_type_script_setup_true_lang.js +1 -1
  102. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +5 -3
  103. package/dist/es/layouts/page-bar/PageBarScrollContainer.vue_vue_type_script_setup_true_lang.js +1 -1
  104. package/dist/es/layouts/page-bar/PageList.vue_vue_type_script_setup_true_lang.js +1 -1
  105. package/dist/es/layouts/page-bar/Search.vue_vue_type_script_setup_true_lang.js +2 -2
  106. package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +50 -7
  107. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +33 -13
  108. package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
  109. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +3 -3
  110. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +26 -38
  111. package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +4 -4
  112. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  113. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +19 -4
  114. package/dist/es/layouts/sidebar/data-source/DataSourceAddButton.vue_vue_type_script_setup_true_lang.js +1 -1
  115. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +23 -6
  116. package/dist/es/layouts/sidebar/data-source/DataSourceList.vue_vue_type_script_setup_true_lang.js +1 -1
  117. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +27 -5
  118. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +19 -4
  119. package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
  120. package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
  121. package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
  122. package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +2 -2
  123. package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +3 -2
  124. package/dist/es/layouts/sidebar/layer/use-click.js +3 -3
  125. package/dist/es/layouts/sidebar/layer/use-node-status.js +2 -2
  126. package/dist/es/layouts/workspace/Breadcrumb.vue_vue_type_script_setup_true_lang.js +1 -1
  127. package/dist/es/layouts/workspace/Workspace.vue_vue_type_script_setup_true_lang.js +1 -1
  128. package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +9 -3
  129. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +2 -2
  130. package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -1
  131. package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +16 -16
  132. package/dist/es/plugin.js +5 -2
  133. package/dist/es/services/codeBlock.js +137 -56
  134. package/dist/es/services/dataSource.js +122 -52
  135. package/dist/es/services/dep.js +162 -41
  136. package/dist/es/services/editor.js +613 -179
  137. package/dist/es/services/events.js +2 -2
  138. package/dist/es/services/history.js +350 -386
  139. package/dist/es/services/keybinding.js +6 -6
  140. package/dist/es/services/props.js +3 -2
  141. package/dist/es/services/storage.js +2 -2
  142. package/dist/es/services/ui.js +10 -2
  143. package/dist/es/style.css +549 -113
  144. package/dist/es/utils/config.js +4 -1
  145. package/dist/es/utils/const.js +2 -2
  146. package/dist/es/utils/content-menu.js +19 -11
  147. package/dist/es/utils/data-source/index.js +34 -11
  148. package/dist/es/utils/dep/collect-worker-client.js +114 -0
  149. package/dist/es/utils/dep/idle-task.js +70 -22
  150. package/dist/es/utils/dep/worker.js +1 -1
  151. package/dist/es/utils/editor.js +103 -49
  152. package/dist/es/utils/event.js +135 -0
  153. package/dist/es/utils/history.js +220 -0
  154. package/dist/es/utils/indexed-db.js +86 -0
  155. package/dist/es/utils/monaco-editor.js +22 -6
  156. package/dist/es/utils/operator.js +2 -2
  157. package/dist/es/utils/props.js +114 -33
  158. package/dist/es/utils/type-match-rules.js +367 -0
  159. package/dist/es/utils/undo-redo.js +60 -1
  160. package/dist/style.css +549 -113
  161. package/dist/themes/magic-admin.css +3028 -0
  162. package/dist/tmagic-editor.umd.cjs +14403 -10121
  163. package/package.json +8 -8
  164. package/src/Editor.vue +43 -2
  165. package/src/components/CodeBlockEditor.vue +3 -0
  166. package/src/components/CompareForm.vue +18 -146
  167. package/src/components/ContentMenu.vue +44 -8
  168. package/src/components/FloatingBox.vue +84 -9
  169. package/src/components/ScrollBar.vue +0 -67
  170. package/src/components/ToolButton.vue +2 -2
  171. package/src/components/ViewForm.vue +55 -0
  172. package/src/editorProps.ts +30 -0
  173. package/src/fields/Code.vue +10 -2
  174. package/src/fields/CodeLink.vue +2 -5
  175. package/src/fields/CodeSelect.vue +54 -40
  176. package/src/fields/CodeSelectCol.vue +8 -1
  177. package/src/fields/CondOpSelect.vue +2 -24
  178. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
  179. package/src/fields/DataSourceFieldSelect/Index.vue +14 -5
  180. package/src/fields/DataSourceFields.vue +42 -8
  181. package/src/fields/DataSourceInput.vue +10 -28
  182. package/src/fields/DataSourceMethodSelect.vue +9 -4
  183. package/src/fields/DataSourceMethods.vue +42 -21
  184. package/src/fields/DataSourceMocks.vue +3 -0
  185. package/src/fields/DisplayConds.vue +18 -1
  186. package/src/fields/EventSelect.vue +152 -166
  187. package/src/fields/StyleSetter/Index.vue +3 -12
  188. package/src/fields/StyleSetter/components/Border.vue +15 -6
  189. package/src/fields/StyleSetter/icons/align-items/Center.vue +19 -0
  190. package/src/fields/StyleSetter/icons/align-items/FlexEnd.vue +19 -0
  191. package/src/fields/StyleSetter/icons/align-items/FlexStart.vue +19 -0
  192. package/src/fields/StyleSetter/icons/align-items/SpaceAround.vue +34 -0
  193. package/src/fields/StyleSetter/icons/align-items/SpaceBetween.vue +34 -0
  194. package/src/fields/StyleSetter/icons/align-items/index.ts +5 -0
  195. package/src/fields/StyleSetter/icons/background-repeat/NoRepeat.vue +20 -4
  196. package/src/fields/StyleSetter/icons/background-repeat/Repeat.vue +10 -28
  197. package/src/fields/StyleSetter/icons/background-repeat/RepeatX.vue +4 -5
  198. package/src/fields/StyleSetter/icons/background-repeat/RepeatY.vue +4 -5
  199. package/src/fields/StyleSetter/icons/flex-direction/Column.vue +4 -3
  200. package/src/fields/StyleSetter/icons/flex-direction/ColumnReverse.vue +4 -3
  201. package/src/fields/StyleSetter/icons/flex-direction/Row.vue +4 -3
  202. package/src/fields/StyleSetter/icons/flex-direction/RowReverse.vue +4 -3
  203. package/src/fields/StyleSetter/icons/justify-content/Center.vue +5 -2
  204. package/src/fields/StyleSetter/icons/justify-content/FlexEnd.vue +15 -2
  205. package/src/fields/StyleSetter/icons/justify-content/FlexStart.vue +15 -2
  206. package/src/fields/StyleSetter/icons/justify-content/SpaceAround.vue +28 -3
  207. package/src/fields/StyleSetter/icons/justify-content/SpaceBetween.vue +28 -2
  208. package/src/fields/StyleSetter/pro/Background.vue +99 -55
  209. package/src/fields/StyleSetter/pro/Border.vue +9 -11
  210. package/src/fields/StyleSetter/pro/Font.vue +66 -64
  211. package/src/fields/StyleSetter/pro/Layout.vue +211 -139
  212. package/src/fields/StyleSetter/pro/Position.vue +69 -67
  213. package/src/fields/StyleSetter/pro/Transform.vue +23 -25
  214. package/src/fields/UISelect.vue +0 -12
  215. package/src/hooks/index.ts +1 -0
  216. package/src/hooks/use-code-block-edit.ts +4 -3
  217. package/src/hooks/use-compare-form.ts +187 -0
  218. package/src/hooks/use-data-source-edit.ts +2 -2
  219. package/src/hooks/use-stage.ts +5 -3
  220. package/src/icons/DatasourceIcon.vue +7 -0
  221. package/src/index.ts +12 -0
  222. package/src/initService.ts +26 -16
  223. package/src/layouts/CodeEditor.vue +34 -11
  224. package/src/layouts/Framework.vue +15 -8
  225. package/src/layouts/NavMenu.vue +3 -3
  226. package/src/layouts/history-list/Bucket.vue +33 -41
  227. package/src/layouts/history-list/BucketTab.vue +72 -0
  228. package/src/layouts/history-list/GroupRow.vue +190 -93
  229. package/src/layouts/history-list/HistoryDiffDialog.vue +169 -90
  230. package/src/layouts/history-list/HistoryListPanel.vue +191 -95
  231. package/src/layouts/history-list/InitialRow.vue +38 -8
  232. package/src/layouts/history-list/PageTab.vue +67 -43
  233. package/src/layouts/history-list/composables.ts +239 -125
  234. package/src/layouts/history-list/useHistoryList.ts +60 -0
  235. package/src/layouts/history-list/useHistoryRevert.ts +425 -0
  236. package/src/layouts/page-bar/PageBar.vue +4 -2
  237. package/src/layouts/props-panel/FormPanel.vue +72 -6
  238. package/src/layouts/props-panel/PropsPanel.vue +62 -18
  239. package/src/layouts/props-panel/use-style-panel.ts +4 -3
  240. package/src/layouts/sidebar/ComponentListPanel.vue +10 -6
  241. package/src/layouts/sidebar/Sidebar.vue +13 -6
  242. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
  243. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +5 -2
  244. package/src/layouts/sidebar/code-block/useContentMenu.ts +14 -2
  245. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +34 -2
  246. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +29 -4
  247. package/src/layouts/sidebar/data-source/useContentMenu.ts +14 -2
  248. package/src/layouts/sidebar/layer/LayerMenu.vue +21 -13
  249. package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
  250. package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
  251. package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
  252. package/src/layouts/sidebar/layer/use-click.ts +3 -3
  253. package/src/layouts/sidebar/layer/use-node-status.ts +2 -2
  254. package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
  255. package/src/layouts/workspace/viewer/Stage.vue +1 -1
  256. package/src/layouts/workspace/viewer/ViewerMenu.vue +15 -15
  257. package/src/plugin.ts +6 -3
  258. package/src/services/codeBlock.ts +152 -47
  259. package/src/services/dataSource.ts +120 -41
  260. package/src/services/dep.ts +292 -47
  261. package/src/services/editor.ts +820 -224
  262. package/src/services/events.ts +3 -3
  263. package/src/services/history.ts +449 -412
  264. package/src/services/keybinding.ts +6 -6
  265. package/src/services/ui.ts +33 -5
  266. package/src/theme/code-block.scss +37 -0
  267. package/src/theme/common/var.scss +1 -1
  268. package/src/theme/component-list-panel.scss +2 -2
  269. package/src/theme/data-source-field.scss +4 -0
  270. package/src/theme/display-conds.scss +11 -0
  271. package/src/theme/event.scss +70 -1
  272. package/src/theme/floating-box.scss +9 -1
  273. package/src/theme/framework.scss +4 -1
  274. package/src/theme/history-list-panel.scss +157 -23
  275. package/src/theme/layer-node-content.scss +14 -0
  276. package/src/theme/nav-menu.scss +5 -1
  277. package/src/theme/page-bar.scss +0 -4
  278. package/src/theme/props-panel.scss +5 -4
  279. package/src/theme/scroll-bar.scss +64 -0
  280. package/src/theme/style-setter/border.scss +17 -5
  281. package/src/theme/theme.scss +4 -0
  282. package/src/theme/themes/magic-admin/index.scss +214 -0
  283. package/src/theme/ui-select.scss +9 -0
  284. package/src/type.ts +668 -124
  285. package/src/utils/config.ts +9 -1
  286. package/src/utils/const.ts +3 -2
  287. package/src/utils/content-menu.ts +20 -11
  288. package/src/utils/data-source/index.ts +32 -12
  289. package/src/utils/dep/collect-worker-client.ts +149 -0
  290. package/src/utils/dep/idle-task.ts +104 -35
  291. package/src/utils/dep/worker.ts +113 -9
  292. package/src/utils/editor.ts +166 -60
  293. package/src/utils/event.ts +237 -0
  294. package/src/utils/history.ts +286 -0
  295. package/src/utils/index.ts +4 -0
  296. package/src/utils/indexed-db.ts +122 -0
  297. package/src/utils/monaco-editor.ts +27 -0
  298. package/src/utils/operator.ts +2 -2
  299. package/src/utils/props.ts +114 -11
  300. package/src/utils/type-match-rules.ts +684 -0
  301. package/src/utils/undo-redo.ts +88 -0
  302. package/types/index.d.ts +1990 -517
  303. package/dist/es/layouts/history-list/CodeBlockTab.js +0 -5
  304. package/dist/es/layouts/history-list/CodeBlockTab.vue_vue_type_script_setup_true_lang.js +0 -62
  305. package/dist/es/layouts/history-list/DataSourceTab.js +0 -5
  306. package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +0 -62
  307. package/src/layouts/history-list/CodeBlockTab.vue +0 -61
  308. package/src/layouts/history-list/DataSourceTab.vue +0 -61
@@ -1,46 +1,27 @@
1
1
  import { Layout, canUsePluginMethods } from "../type.js";
2
2
  import BaseService from "./BaseService.js";
3
3
  import props_default from "./props.js";
4
+ import { getLastPushedHistoryIds } from "../utils/history.js";
4
5
  import history_default from "./history.js";
5
6
  import storage_default, { Protocol } from "./storage.js";
6
- import { COPY_STORAGE_KEY, calcAlignCenterStyle, calcLayerTargetIndex, calcMoveStyle, classifyDragSources, collectRelatedNodes, editorNodeMergeCustomizer, fixNodePosition, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, moveItemsInContainer, resolveSelectedNode, setChildrenLayout, setLayout, toggleFixedPosition } from "../utils/editor.js";
7
+ import { COPY_STORAGE_KEY, calcAlignCenterStyle, calcLayerTargetIndex, calcMoveStyle, classifyDragSources, collectRelatedNodes, describeStepForRevert, editorNodeMergeCustomizer, fixNodePosition, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, moveItemsInContainer, resolveSelectedNode, setChildrenLayout, setLayout, toggleFixedPosition } from "../utils/editor.js";
7
8
  import { beforePaste, getAddParent } from "../utils/operator.js";
8
9
  import { isFixed } from "@tmagic/stage";
9
- import { getNodeInfo, getNodePath, getValueByKeyPath, isPage, isPageFragment, setValueByKeyPath } from "@tmagic/utils";
10
+ import { getNodeInfo, getNodePath, getValueByKeyPath, guid, isPage, isPageFragment, isPageOrFragment, setValueByKeyPath, traverseNode } from "@tmagic/utils";
10
11
  import { NodeType } from "@tmagic/core";
11
- import { reactive, toRaw } from "vue";
12
- import { cloneDeep as cloneDeep$1, isObject, mergeWith, uniq } from "lodash-es";
12
+ import { nextTick, reactive, toRaw } from "vue";
13
+ import { cloneDeep as cloneDeep$1, isEmpty, isEqual, isObject, mergeWith, uniq } from "lodash-es";
13
14
  //#region packages/editor/src/services/editor.ts
14
15
  /**
15
- * 给「回滚」生成的新 step 用的简短描述生成器。
16
- * UI `describePageStep` 同义,但避免 service 反向依赖 layouts/,故在此本地实现。
16
+ * 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
17
+ * `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
18
+ * 缺省(未传 / 空数组)才退化为整节点替换。
17
19
  */
18
- var describeStepForRevert = (step) => {
19
- switch (step.opType) {
20
- case "add": {
21
- const count = step.nodes?.length ?? 0;
22
- const node = step.nodes?.[0];
23
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
24
- return `撤回新增 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
25
- }
26
- case "remove": {
27
- const count = step.removedItems?.length ?? 0;
28
- const node = step.removedItems?.[0]?.node;
29
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
30
- return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
31
- }
32
- default: {
33
- const items = step.updatedItems ?? [];
34
- if (items.length === 1) {
35
- const { newNode, oldNode, changeRecords } = items[0];
36
- const target = newNode?.name || newNode?.type || oldNode?.name || oldNode?.type || `${newNode?.id ?? ""}`;
37
- const propPath = changeRecords?.[0]?.propPath;
38
- return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
39
- }
40
- return `还原 ${items.length} 个节点的修改`;
41
- }
42
- }
43
- };
20
+ var buildUpdateDiff = (items) => items.map(({ oldNode, newNode, changeRecords }) => ({
21
+ oldSchema: oldNode,
22
+ newSchema: newNode,
23
+ ...changeRecords?.length ? { changeRecords } : {}
24
+ }));
44
25
  var Editor = class extends BaseService {
45
26
  state = reactive({
46
27
  root: null,
@@ -52,12 +33,24 @@ var Editor = class extends BaseService {
52
33
  stageLoading: true,
53
34
  highlightNode: null,
54
35
  modifiedNodeIds: /* @__PURE__ */ new Map(),
36
+ invalidNodeIds: /* @__PURE__ */ new Map(),
55
37
  pageLength: 0,
56
38
  pageFragmentLength: 0,
57
39
  disabledMultiSelect: false,
58
40
  alwaysMultiSelect: false
59
41
  });
60
42
  selectionBeforeOp = null;
43
+ /**
44
+ * 操作前的节点校验错误快照,与 selectionBeforeOp 同时在 captureSelectionBeforeOp 中捕获,
45
+ * 供 pushOpHistory 写入 step.extra.invalidNodeIdsBefore,用于撤销时还原到「操作前」的错误状态。
46
+ */
47
+ invalidNodeIdsBeforeOp = null;
48
+ /**
49
+ * 最近一次 pushOpHistory 写入的历史记录 uuid。
50
+ * 供 *AndGetHistoryId 系列方法在调用普通操作后取回本次产生的历史记录 id;
51
+ * 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
52
+ */
53
+ lastPushedHistoryId = null;
61
54
  constructor() {
62
55
  super(canUsePluginMethods.async.map((methodName) => ({
63
56
  name: methodName,
@@ -70,10 +63,11 @@ var Editor = class extends BaseService {
70
63
  }
71
64
  /**
72
65
  * 设置当前指点节点配置
73
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
66
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength
74
67
  * @param value MNode
68
+ * @param options.historySource 设置 root 时,本次变更写入历史记录的「操作来源」(仅 name === 'root' 时生效)
75
69
  */
76
- set(name, value) {
70
+ set(name, value, options = {}) {
77
71
  const preValue = this.state[name];
78
72
  this.state[name] = value;
79
73
  if (name === "nodes" && Array.isArray(value)) this.set("node", value[0]);
@@ -84,17 +78,25 @@ var Editor = class extends BaseService {
84
78
  this.state.pageLength = getPageList(app).length || 0;
85
79
  this.state.pageFragmentLength = getPageFragmentList(app).length || 0;
86
80
  this.state.stageLoading = this.state.pageLength !== 0;
81
+ if (preValue && !isEmpty(preValue)) this.pushRootDiffHistory(preValue, app, options.historySource);
82
+ else app.items?.forEach((pageNode) => {
83
+ if (pageNode?.id !== void 0 && !history_default.getMarker("page", pageNode.id)) history_default.setMarker("page", pageNode.id, {
84
+ name: pageNode.name,
85
+ source: options.historySource
86
+ });
87
+ });
87
88
  } else {
88
89
  this.state.pageLength = 0;
89
90
  this.state.pageFragmentLength = 0;
90
91
  this.state.stageLoading = false;
91
92
  }
92
- this.emit("root-change", value, preValue);
93
+ this.emit("root-change", value, preValue, options);
94
+ this.pruneInvalidNodeIds();
93
95
  }
94
96
  }
95
97
  /**
96
98
  * 获取当前指点节点配置
97
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength'
99
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength'
98
100
  * @returns MNode
99
101
  */
100
102
  get(name) {
@@ -109,7 +111,25 @@ var Editor = class extends BaseService {
109
111
  getNodeInfo(id, raw = true) {
110
112
  let root = this.get("root");
111
113
  if (raw) root = toRaw(root);
112
- return getNodeInfo(id, root);
114
+ if (!root) return {
115
+ node: null,
116
+ parent: null,
117
+ page: null,
118
+ path: []
119
+ };
120
+ if (id === root.id) return {
121
+ node: root,
122
+ parent: null,
123
+ page: null,
124
+ path: []
125
+ };
126
+ const pageIdStr = `${this.get("page")?.id || ""}`;
127
+ const currentPageNode = root.items?.find((item) => `${item.id}` === pageIdStr);
128
+ if (currentPageNode && `${id}` !== pageIdStr) {
129
+ const info = getNodeInfo(id, currentPageNode);
130
+ if (info.node) return info;
131
+ }
132
+ return getNodeInfo(id, root, currentPageNode);
113
133
  }
114
134
  /**
115
135
  * 根据ID获取指点节点配置
@@ -139,7 +159,7 @@ var Editor = class extends BaseService {
139
159
  isOnDifferentPage(node) {
140
160
  const currentPageId = this.get("page")?.id;
141
161
  if (currentPageId === void 0 || currentPageId === null) return false;
142
- if (isPage(node) || isPageFragment(node)) return `${node.id}` !== `${currentPageId}`;
162
+ if (isPageOrFragment(node)) return `${node.id}` !== `${currentPageId}`;
143
163
  const nodePage = this.getNodeInfo(node.id, false).page;
144
164
  if (!nodePage) return false;
145
165
  return `${nodePage.id}` !== `${currentPageId}`;
@@ -163,8 +183,6 @@ var Editor = class extends BaseService {
163
183
  this.set("nodes", node ? [node] : []);
164
184
  this.set("page", page);
165
185
  this.set("parent", parent);
166
- if (page) history_default.changePage(toRaw(page));
167
- else history_default.resetState();
168
186
  if (node?.id) this.get("stage")?.renderer?.runtime?.getApp?.()?.page?.emit("editor:select", {
169
187
  node,
170
188
  page,
@@ -234,8 +252,8 @@ var Editor = class extends BaseService {
234
252
  const curNode = this.get("node");
235
253
  const stage = this.get("stage");
236
254
  if (!curNode) throw new Error("当前选中节点为空");
237
- if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !(isPage(node) || isPageFragment(node))) throw new Error("app下不能添加组件");
238
- if (parent.id !== curNode.id && !(isPage(node) || isPageFragment(node))) {
255
+ if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !isPageOrFragment(node)) throw new Error("app下不能添加组件");
256
+ if (parent.id !== curNode.id && !isPageOrFragment(node)) {
239
257
  const index = parent.items.indexOf(curNode);
240
258
  parent.items?.splice(index + 1, 0, node);
241
259
  } else parent.items?.push(node);
@@ -269,7 +287,7 @@ var Editor = class extends BaseService {
269
287
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
270
288
  * @returns 添加后的节点
271
289
  */
272
- async add(addNode, parent, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
290
+ async add(addNode, parent, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
273
291
  this.captureSelectionBeforeOp();
274
292
  const stage = this.get("stage");
275
293
  const addNodes = [];
@@ -280,7 +298,7 @@ var Editor = class extends BaseService {
280
298
  } else addNodes.push(...addNode);
281
299
  const newNodes = await Promise.all(addNodes.map((node) => {
282
300
  const root = this.get("root");
283
- if ((isPage(node) || isPageFragment(node)) && root) return this.doAdd(node, root);
301
+ if (isPageOrFragment(node) && root) return this.doAdd(node, root);
284
302
  const parentNode = parent ?? getAddParent(node);
285
303
  if (!parentNode) throw new Error("未找到父元素");
286
304
  return this.doAdd(node, parentNode);
@@ -300,22 +318,44 @@ var Editor = class extends BaseService {
300
318
  else if (isPageFragment(newNodes[0])) this.state.pageFragmentLength += 1;
301
319
  else if (!skipSelect) stage?.select(newNodes[0].id);
302
320
  }
303
- if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
321
+ if (!isPageOrFragment(newNodes[0])) {
304
322
  const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
305
- if (!doNotPushHistory) this.pushOpHistory("add", {
306
- nodes: newNodes.map((n) => cloneDeep$1(toRaw(n))),
307
- parentId: (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id,
308
- indexMap: Object.fromEntries(newNodes.map((n) => {
309
- const p = this.getParentById(n.id, false);
310
- return [n.id, p ? getNodeIndex(n.id, p) : -1];
311
- }))
312
- }, {
313
- name: pageForOp?.name || "",
314
- id: pageForOp.id
315
- }, historyDescription);
316
- else this.selectionBeforeOp = null;
323
+ if (!doNotPushHistory) {
324
+ const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id;
325
+ this.pushOpHistory("add", {
326
+ diff: newNodes.map((n) => {
327
+ const p = this.getParentById(n.id, false);
328
+ const idx = p ? getNodeIndex(n.id, p) : -1;
329
+ return {
330
+ newSchema: cloneDeep$1(toRaw(n)),
331
+ parentId,
332
+ index: typeof idx === "number" ? idx : -1
333
+ };
334
+ }),
335
+ pageData: {
336
+ name: pageForOp?.name || "",
337
+ id: pageForOp.id
338
+ },
339
+ historyDescription,
340
+ source: historySource
341
+ });
342
+ } else this.selectionBeforeOp = null;
317
343
  }
318
344
  this.emit("add", newNodes);
345
+ this.emit("change", {
346
+ type: "add",
347
+ data: newNodes.map((node) => ({
348
+ node,
349
+ page: this.getPageOfNode(node.id)
350
+ })),
351
+ historySource,
352
+ doNotPushHistory
353
+ });
354
+ const addedPages = newNodes.filter((node) => isPageOrFragment(node));
355
+ if (addedPages.length) history_default.notifyPageStructureChange({
356
+ add: addedPages,
357
+ remove: []
358
+ });
319
359
  return Array.isArray(addNode) ? newNodes : newNodes[0];
320
360
  }
321
361
  async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
@@ -325,13 +365,10 @@ var Editor = class extends BaseService {
325
365
  if (!parent || !curNode) throw new Error("找不要删除的节点");
326
366
  const index = getNodeIndex(curNode.id, parent);
327
367
  if (typeof index !== "number" || index === -1) throw new Error("找不要删除的节点");
328
- parent.items?.splice(index, 1);
329
368
  const stage = this.get("stage");
330
- stage?.remove({
331
- id: node.id,
332
- parentId: parent.id,
333
- root: cloneDeep$1(root)
334
- });
369
+ const currentPage = this.get("page");
370
+ const isDeletingCurrentPage = isPageOrFragment(node) && !!currentPage && `${currentPage.id}` === `${node.id}`;
371
+ parent.items?.splice(index, 1);
335
372
  const selectedNodes = this.get("nodes");
336
373
  const removedSelectedIndex = selectedNodes.findIndex((n) => `${n.id}` === `${node.id}`);
337
374
  if (removedSelectedIndex !== -1) {
@@ -339,27 +376,29 @@ var Editor = class extends BaseService {
339
376
  nextSelected.splice(removedSelectedIndex, 1);
340
377
  this.set("nodes", nextSelected);
341
378
  }
342
- if (isPage(node) || isPageFragment(node)) {
343
- const currentPage = this.get("page");
344
- if (currentPage && `${currentPage.id}` === `${node.id}`) this.set("page", null);
345
- }
346
- const selectDefault = async (pages) => {
347
- if (pages[0]) {
348
- await this.select(pages[0]);
349
- stage?.select(pages[0].id);
350
- } else {
351
- this.selectRoot();
352
- history_default.resetPage();
353
- }
379
+ const removeData = {
380
+ id: node.id,
381
+ parentId: parent.id,
382
+ root: cloneDeep$1(root)
354
383
  };
355
384
  const rootItems = root.items || [];
356
- if (isPage(node)) {
357
- this.state.pageLength -= 1;
358
- if (!doNotSelect && !doNotSwitchPage) await selectDefault(rootItems);
359
- } else if (isPageFragment(node)) {
360
- this.state.pageFragmentLength -= 1;
361
- if (!doNotSelect && !doNotSwitchPage) await selectDefault(rootItems);
385
+ const shouldReselect = isDeletingCurrentPage && !doNotSelect && !doNotSwitchPage;
386
+ const shouldSwitchPage = shouldReselect && rootItems.length > 0;
387
+ if (isPageOrFragment(node)) {
388
+ if (isPage(node)) this.state.pageLength -= 1;
389
+ else this.state.pageFragmentLength -= 1;
390
+ if (shouldSwitchPage) {
391
+ await this.select(rootItems[0]);
392
+ stage?.select(rootItems[0].id);
393
+ await nextTick();
394
+ stage?.remove(removeData);
395
+ } else {
396
+ stage?.remove(removeData);
397
+ if (shouldReselect) this.selectRoot();
398
+ else if (isDeletingCurrentPage) this.set("page", null);
399
+ }
362
400
  } else {
401
+ stage?.remove(removeData);
363
402
  if (!doNotSelect) {
364
403
  await this.select(parent);
365
404
  stage?.select(parent.id);
@@ -379,12 +418,16 @@ var Editor = class extends BaseService {
379
418
  * @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
380
419
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
381
420
  */
382
- async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
421
+ async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
383
422
  this.captureSelectionBeforeOp();
384
423
  const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
424
+ const changeItems = nodes.map((node) => ({
425
+ node,
426
+ page: this.getPageOfNode(node.id)
427
+ }));
385
428
  const removedItems = [];
386
429
  let pageForOp = null;
387
- if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) for (const n of nodes) {
430
+ if (!isPageOrFragment(nodes[0])) for (const n of nodes) {
388
431
  const { parent, node: curNode, page } = this.getNodeInfo(n.id, false);
389
432
  if (parent && curNode) {
390
433
  if (!pageForOp && page) pageForOp = {
@@ -393,7 +436,7 @@ var Editor = class extends BaseService {
393
436
  };
394
437
  const idx = getNodeIndex(curNode.id, parent);
395
438
  removedItems.push({
396
- node: cloneDeep$1(toRaw(curNode)),
439
+ oldSchema: cloneDeep$1(toRaw(curNode)),
397
440
  parentId: parent.id,
398
441
  index: typeof idx === "number" ? idx : -1
399
442
  });
@@ -403,22 +446,38 @@ var Editor = class extends BaseService {
403
446
  doNotSelect,
404
447
  doNotSwitchPage
405
448
  })));
406
- if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", { removedItems }, pageForOp, historyDescription);
449
+ this.removeInvalidNodesBySubtree(nodes);
450
+ if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", {
451
+ diff: removedItems,
452
+ pageData: pageForOp,
453
+ historyDescription,
454
+ source: historySource
455
+ });
407
456
  else this.selectionBeforeOp = null;
408
457
  this.emit("remove", nodes);
458
+ this.emit("change", {
459
+ type: "remove",
460
+ data: changeItems,
461
+ historySource,
462
+ doNotPushHistory
463
+ });
464
+ const removedPages = nodes.filter((node) => isPageOrFragment(node));
465
+ if (removedPages.length) history_default.notifyPageStructureChange({
466
+ add: [],
467
+ remove: removedPages
468
+ });
409
469
  }
410
- async doUpdate(config, { changeRecords = [] } = {}) {
411
- const root = this.get("root");
412
- if (!root) throw new Error("root为空");
470
+ async doUpdate(config, { changeRecords = [], historySource } = {}) {
471
+ if (!this.get("root")) throw new Error("root为空");
413
472
  if (!config?.id) throw new Error("没有配置或者配置缺少id值");
414
473
  const info = this.getNodeInfo(config.id, false);
415
474
  if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
416
475
  const node = toRaw(info.node);
417
- let newConfig = await toggleFixedPosition(toRaw(config), node, root, this.getLayout);
476
+ let newConfig = await toggleFixedPosition(toRaw(config), node, info.path, this.getLayout);
418
477
  newConfig = mergeWith(cloneDeep$1(node), newConfig, editorNodeMergeCustomizer);
419
478
  if (!newConfig.type) throw new Error("配置缺少type值");
420
479
  if (newConfig.type === NodeType.ROOT) {
421
- this.set("root", newConfig);
480
+ this.set("root", newConfig, { historySource });
422
481
  return {
423
482
  oldNode: node,
424
483
  newNode: newConfig,
@@ -440,7 +499,7 @@ var Editor = class extends BaseService {
440
499
  selectedNodes.splice(targetIndex, 1, newConfig);
441
500
  this.set("nodes", [...selectedNodes]);
442
501
  }
443
- if (isPage(newConfig) || isPageFragment(newConfig)) {
502
+ if (isPageOrFragment(newConfig)) {
444
503
  const currentPage = this.get("page");
445
504
  if (currentPage && `${currentPage.id}` === `${newConfig.id}`) this.set("page", newConfig);
446
505
  }
@@ -464,26 +523,44 @@ var Editor = class extends BaseService {
464
523
  */
465
524
  async update(config, data = {}) {
466
525
  this.captureSelectionBeforeOp();
467
- const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription } = data;
526
+ const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription, historySource, invalidInfo } = data;
468
527
  const nodes = Array.isArray(config) ? config : [config];
469
528
  const updateData = await Promise.all(nodes.map((node, index) => {
470
529
  const recordsForNode = changeRecordList ? changeRecordList[index] ?? [] : changeRecords ?? [];
471
- return this.doUpdate(node, { changeRecords: recordsForNode });
530
+ return this.doUpdate(node, {
531
+ changeRecords: recordsForNode,
532
+ historySource
533
+ });
472
534
  }));
535
+ this.applyInvalidInfo(config, invalidInfo);
473
536
  if (updateData[0].oldNode?.type !== NodeType.ROOT) {
474
537
  if (this.get("nodes").length) if (!doNotPushHistory) {
475
538
  const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
476
- this.pushOpHistory("update", { updatedItems: updateData.map((d) => ({
477
- oldNode: cloneDeep$1(d.oldNode),
478
- newNode: cloneDeep$1(toRaw(d.newNode)),
479
- changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
480
- })) }, {
481
- name: pageForOp?.name || "",
482
- id: pageForOp.id
483
- }, historyDescription);
539
+ this.pushOpHistory("update", {
540
+ diff: buildUpdateDiff(updateData.map((d) => ({
541
+ oldNode: cloneDeep$1(d.oldNode),
542
+ newNode: cloneDeep$1(d.newNode),
543
+ changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
544
+ }))),
545
+ pageData: {
546
+ name: pageForOp?.name || "",
547
+ id: pageForOp.id
548
+ },
549
+ historyDescription,
550
+ source: historySource
551
+ });
484
552
  } else this.selectionBeforeOp = null;
485
553
  }
486
554
  this.emit("update", updateData);
555
+ this.emit("change", {
556
+ type: "update",
557
+ data: updateData.map((node) => ({
558
+ node,
559
+ page: this.getPageOfNode(node.newNode.id)
560
+ })),
561
+ historySource,
562
+ doNotPushHistory
563
+ });
487
564
  return Array.isArray(config) ? updateData.map((item) => item.newNode) : updateData[0].newNode;
488
565
  }
489
566
  /**
@@ -496,7 +573,7 @@ var Editor = class extends BaseService {
496
573
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
497
574
  * @returns void
498
575
  */
499
- async sort(id1, id2, { doNotSelect = false, doNotPushHistory = false } = {}) {
576
+ async sort(id1, id2, { doNotSelect = false, doNotPushHistory = false, historySource } = {}) {
500
577
  this.captureSelectionBeforeOp();
501
578
  const root = this.get("root");
502
579
  if (!root) throw new Error("root为空");
@@ -508,7 +585,10 @@ var Editor = class extends BaseService {
508
585
  if (index2 < 0) return;
509
586
  const index1 = parent.items.findIndex((node) => `${node.id}` === `${id1}`);
510
587
  parent.items.splice(index2, 0, ...parent.items.splice(index1, 1));
511
- await this.update(parent, { doNotPushHistory });
588
+ await this.update(parent, {
589
+ doNotPushHistory,
590
+ historySource
591
+ });
512
592
  if (!doNotSelect) await this.select(node);
513
593
  this.get("stage")?.update({
514
594
  config: cloneDeep$1(node),
@@ -544,7 +624,7 @@ var Editor = class extends BaseService {
544
624
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
545
625
  * @returns 添加后的组件节点配置
546
626
  */
547
- async paste(position = {}, collectorOptions, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
627
+ async paste(position = {}, collectorOptions, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
548
628
  const config = storage_default.getItem(COPY_STORAGE_KEY);
549
629
  if (!Array.isArray(config)) return;
550
630
  const node = this.get("node");
@@ -558,7 +638,9 @@ var Editor = class extends BaseService {
558
638
  return this.add(pasteConfigs, parent, {
559
639
  doNotSelect,
560
640
  doNotSwitchPage,
561
- doNotPushHistory
641
+ doNotPushHistory,
642
+ historyDescription,
643
+ historySource
562
644
  });
563
645
  }
564
646
  async doPaste(config, position = {}) {
@@ -586,11 +668,15 @@ var Editor = class extends BaseService {
586
668
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
587
669
  * @returns 当前组件节点配置
588
670
  */
589
- async alignCenter(config, { doNotSelect = false, doNotPushHistory = false } = {}) {
671
+ async alignCenter(config, { doNotSelect = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
590
672
  const nodes = Array.isArray(config) ? config : [config];
591
673
  const stage = this.get("stage");
592
674
  const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
593
- const newNode = await this.update(newNodes, { doNotPushHistory });
675
+ const newNode = await this.update(newNodes, {
676
+ doNotPushHistory,
677
+ historyDescription,
678
+ historySource
679
+ });
594
680
  if (!doNotSelect) if (newNodes.length > 1) await stage?.multiSelect(newNodes.map((node) => node.id));
595
681
  else await stage?.select(newNodes[0].id);
596
682
  return newNode;
@@ -601,7 +687,7 @@ var Editor = class extends BaseService {
601
687
  * @param options 可选配置
602
688
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
603
689
  */
604
- async moveLayer(offset, { doNotPushHistory = false } = {}) {
690
+ async moveLayer(offset, { doNotPushHistory = false, historyDescription, historySource } = {}) {
605
691
  this.captureSelectionBeforeOp();
606
692
  const root = this.get("root");
607
693
  if (!root) throw new Error("root为空");
@@ -626,15 +712,30 @@ var Editor = class extends BaseService {
626
712
  this.addModifiedNodeId(parent.id);
627
713
  if (!doNotPushHistory) {
628
714
  const pageForOp = this.getNodeInfo(node.id, false).page;
629
- this.pushOpHistory("update", { updatedItems: [{
630
- oldNode: oldParent,
631
- newNode: cloneDeep$1(toRaw(parent))
632
- }] }, {
633
- name: pageForOp?.name || "",
634
- id: pageForOp.id
715
+ this.pushOpHistory("update", {
716
+ diff: buildUpdateDiff([{
717
+ oldNode: oldParent,
718
+ newNode: cloneDeep$1(toRaw(parent))
719
+ }]),
720
+ pageData: {
721
+ name: pageForOp?.name || "",
722
+ id: pageForOp.id
723
+ },
724
+ historyDescription,
725
+ source: historySource
635
726
  });
636
727
  } else this.selectionBeforeOp = null;
637
728
  this.emit("move-layer", offset);
729
+ this.emit("change", {
730
+ type: "move-layer",
731
+ data: [{
732
+ node,
733
+ page: this.getPageOfNode(node.id)
734
+ }],
735
+ offset,
736
+ historySource,
737
+ doNotPushHistory
738
+ });
638
739
  }
639
740
  /**
640
741
  * 移动一个或多个节点到指定容器中。
@@ -650,9 +751,9 @@ var Editor = class extends BaseService {
650
751
  * @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
651
752
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
652
753
  */
653
- async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
754
+ async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
654
755
  const isBatch = Array.isArray(config);
655
- const configs = (isBatch ? config : [config]).filter((item) => !(isPage(item) || isPageFragment(item)));
756
+ const configs = (isBatch ? config : [config]).filter((item) => !isPageOrFragment(item));
656
757
  if (configs.length === 0) throw new Error("没有可移动的节点");
657
758
  this.captureSelectionBeforeOp();
658
759
  const target = this.getNodeById(targetId, false);
@@ -698,11 +799,16 @@ var Editor = class extends BaseService {
698
799
  name: "",
699
800
  id: target.id
700
801
  };
701
- this.pushOpHistory("update", { updatedItems }, historyPage);
802
+ this.pushOpHistory("update", {
803
+ diff: buildUpdateDiff(updatedItems),
804
+ pageData: historyPage,
805
+ historyDescription,
806
+ source: historySource
807
+ });
702
808
  } else this.selectionBeforeOp = null;
703
809
  return isBatch ? newConfigs : newConfigs[0];
704
810
  }
705
- async dragTo(config, targetParent, targetIndex, { doNotPushHistory = false } = {}) {
811
+ async dragTo(config, targetParent, targetIndex, { doNotPushHistory = false, historyDescription, historySource } = {}) {
706
812
  this.captureSelectionBeforeOp();
707
813
  if (!targetParent || !Array.isArray(targetParent.items)) return;
708
814
  const configs = Array.isArray(config) ? config : [config];
@@ -744,9 +850,14 @@ var Editor = class extends BaseService {
744
850
  }
745
851
  if (!doNotPushHistory) {
746
852
  const pageForOp = this.getNodeInfo(configs[0].id, false).page;
747
- this.pushOpHistory("update", { updatedItems }, {
748
- name: pageForOp?.name || "",
749
- id: pageForOp.id
853
+ this.pushOpHistory("update", {
854
+ diff: buildUpdateDiff(updatedItems),
855
+ pageData: {
856
+ name: pageForOp?.name || "",
857
+ id: pageForOp.id
858
+ },
859
+ historyDescription,
860
+ source: historySource
750
861
  });
751
862
  } else this.selectionBeforeOp = null;
752
863
  this.emit("drag-to", {
@@ -754,13 +865,84 @@ var Editor = class extends BaseService {
754
865
  configs,
755
866
  targetParent
756
867
  });
868
+ this.emit("change", {
869
+ type: "drag-to",
870
+ data: configs.map((node) => ({
871
+ node,
872
+ page: this.getPageOfNode(node.id)
873
+ })),
874
+ targetIndex,
875
+ targetParent,
876
+ historySource,
877
+ doNotPushHistory
878
+ });
879
+ }
880
+ /**
881
+ * 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
882
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link StepValue.uuid}),
883
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
884
+ *
885
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时 historyIds 为 `[]`。
886
+ */
887
+ /** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
888
+ async addAndGetHistoryId(addNode, parent, options = {}) {
889
+ this.lastPushedHistoryId = null;
890
+ return {
891
+ result: await this.add(addNode, parent, options),
892
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
893
+ };
894
+ }
895
+ /** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
896
+ async removeAndGetHistoryId(nodeOrNodeList, options = {}) {
897
+ this.lastPushedHistoryId = null;
898
+ await this.remove(nodeOrNodeList, options);
899
+ return {
900
+ result: void 0,
901
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
902
+ };
903
+ }
904
+ /** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
905
+ async updateAndGetHistoryId(config, data = {}) {
906
+ this.lastPushedHistoryId = null;
907
+ return {
908
+ result: await this.update(config, data),
909
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
910
+ };
911
+ }
912
+ /** 等价于 {@link moveLayer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
913
+ async moveLayerAndGetHistoryId(offset, options = {}) {
914
+ this.lastPushedHistoryId = null;
915
+ await this.moveLayer(offset, options);
916
+ return {
917
+ result: void 0,
918
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
919
+ };
920
+ }
921
+ /** 等价于 {@link moveToContainer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
922
+ async moveToContainerAndGetHistoryId(config, targetId, options = {}) {
923
+ this.lastPushedHistoryId = null;
924
+ return {
925
+ result: await this.moveToContainer(config, targetId, options),
926
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
927
+ };
928
+ }
929
+ /** 等价于 {@link dragTo},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
930
+ async dragToAndGetHistoryId(config, targetParent, targetIndex, options = {}) {
931
+ this.lastPushedHistoryId = null;
932
+ await this.dragTo(config, targetParent, targetIndex, options);
933
+ return {
934
+ result: void 0,
935
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
936
+ };
757
937
  }
758
938
  /**
759
939
  * 撤销当前操作
760
940
  * @returns 被撤销的操作
761
941
  */
762
942
  async undo() {
763
- const value = history_default.undo();
943
+ const pageId = this.get("page")?.id;
944
+ if (pageId === void 0) return null;
945
+ const value = history_default.undo("page", pageId);
764
946
  if (value) await this.applyHistoryOp(value, true);
765
947
  return value;
766
948
  }
@@ -769,7 +951,9 @@ var Editor = class extends BaseService {
769
951
  * @returns 被恢复的操作
770
952
  */
771
953
  async redo() {
772
- const value = history_default.redo();
954
+ const pageId = this.get("page")?.id;
955
+ if (pageId === void 0) return null;
956
+ const value = history_default.redo("page", pageId);
773
957
  if (value) await this.applyHistoryOp(value, false);
774
958
  return value;
775
959
  }
@@ -790,12 +974,17 @@ var Editor = class extends BaseService {
790
974
  * @returns 反向后产生的新 step;目标不存在 / 未应用 / 反向失败时返回 null
791
975
  */
792
976
  async revertPageStep(index) {
793
- const entry = history_default.getPageStepList()[index];
977
+ const entry = history_default.getStepList("page", this.get("page")?.id)[index];
794
978
  if (!entry?.applied) return null;
795
979
  const { step } = entry;
980
+ if (step.opType === "initial") return null;
796
981
  if (!this.get("root")) return null;
982
+ if (step.opType === "update") {
983
+ const items = step.diff ?? [];
984
+ if (!items.length || !items.every((item) => item.changeRecords?.length)) return null;
985
+ }
797
986
  let revertedStep = null;
798
- const captureRevert = (s) => {
987
+ const captureRevert = (_pageId, s) => {
799
988
  revertedStep = s;
800
989
  };
801
990
  history_default.once("change", captureRevert);
@@ -803,28 +992,31 @@ var Editor = class extends BaseService {
803
992
  const opts = {
804
993
  doNotSelect: true,
805
994
  doNotSwitchPage: true,
806
- historyDescription
995
+ historyDescription,
996
+ historySource: "rollback"
807
997
  };
808
998
  try {
809
999
  switch (step.opType) {
810
- case "add": {
811
- const nodes = step.nodes ?? [];
812
- for (const n of nodes) {
813
- const existing = this.getNodeById(n.id, false);
1000
+ case "add":
1001
+ for (const { newSchema } of step.diff ?? []) {
1002
+ if (!newSchema) continue;
1003
+ const existing = this.getNodeById(newSchema.id, false);
814
1004
  if (existing) await this.remove(existing, opts);
815
1005
  }
816
1006
  break;
817
- }
818
1007
  case "remove": {
819
- const sorted = [...step.removedItems ?? []].sort((a, b) => a.index - b.index);
820
- for (const { node, parentId } of sorted) {
1008
+ const sorted = [...step.diff ?? []].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
1009
+ for (const { oldSchema, parentId } of sorted) {
1010
+ if (!oldSchema || parentId === void 0) continue;
821
1011
  const parent = this.getNodeById(parentId, false);
822
- if (parent) await this.add([cloneDeep$1(node)], parent, opts);
1012
+ if (parent) await this.add([cloneDeep$1(oldSchema)], parent, opts);
823
1013
  }
824
1014
  break;
825
1015
  }
826
1016
  case "update": {
827
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
1017
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
1018
+ const oldNode = oldSchema;
1019
+ const newNode = newSchema;
828
1020
  if (changeRecords?.length) {
829
1021
  const patch = {
830
1022
  id: newNode.id,
@@ -839,7 +1031,10 @@ var Editor = class extends BaseService {
839
1031
  }
840
1032
  return cloneDeep$1(oldNode);
841
1033
  });
842
- if (configs.length) await this.update(configs, { historyDescription });
1034
+ if (configs.length) await this.update(configs, {
1035
+ historyDescription,
1036
+ historySource: "rollback"
1037
+ });
843
1038
  break;
844
1039
  }
845
1040
  }
@@ -851,6 +1046,22 @@ var Editor = class extends BaseService {
851
1046
  return revertedStep;
852
1047
  }
853
1048
  /**
1049
+ * 通过历史记录 uuid 回滚当前页面的历史步骤,语义与 {@link revertPageStep} 完全一致,
1050
+ * 仅入参从 index 改为 uuid 列表({@link StepValue.uuid})。按数组顺序依次回滚,
1051
+ * 返回与入参同序的结果列表(某项失败时为 `null`)。
1052
+ *
1053
+ * @param uuids 目标历史记录的 uuid 列表,通常由 *AndGetHistoryId 方法返回的 `historyIds`
1054
+ */
1055
+ async revertPageStepById(uuids) {
1056
+ const results = [];
1057
+ const pageId = this.get("page")?.id;
1058
+ for (const uuid of uuids) {
1059
+ const location = history_default.findStepLocationByUuid("page", uuid, pageId);
1060
+ results.push(!location ? null : await this.revertPageStep(location.index));
1061
+ }
1062
+ return results;
1063
+ }
1064
+ /**
854
1065
  * 跳转当前页面历史栈到指定游标位置。
855
1066
  *
856
1067
  * `targetCursor` 与 `UndoRedo.getCursor()` 同义:表示"已应用步骤数量",
@@ -860,8 +1071,9 @@ var Editor = class extends BaseService {
860
1071
  * @returns 实际移动到的最终游标位置
861
1072
  */
862
1073
  async gotoPageStep(targetCursor) {
863
- let cursor = history_default.getPageCursor();
864
- const { length } = history_default.getPageStepList();
1074
+ const pageId = this.get("page")?.id;
1075
+ let cursor = history_default.getCursor("page", pageId);
1076
+ const { length } = history_default.getStepList("page", pageId);
865
1077
  const target = Math.max(0, Math.min(targetCursor, length));
866
1078
  while (cursor > target) {
867
1079
  if (!await this.undo()) break;
@@ -873,7 +1085,7 @@ var Editor = class extends BaseService {
873
1085
  }
874
1086
  return cursor;
875
1087
  }
876
- async move(left, top, { doNotPushHistory = false } = {}) {
1088
+ async move(left, top, { doNotPushHistory = false, historyDescription, historySource } = {}) {
877
1089
  const node = toRaw(this.get("node"));
878
1090
  if (!node || isPage(node)) return;
879
1091
  const newStyle = calcMoveStyle(node.style || {}, left, top);
@@ -882,7 +1094,11 @@ var Editor = class extends BaseService {
882
1094
  id: node.id,
883
1095
  type: node.type,
884
1096
  style: newStyle
885
- }, { doNotPushHistory });
1097
+ }, {
1098
+ doNotPushHistory,
1099
+ historyDescription,
1100
+ historySource
1101
+ });
886
1102
  }
887
1103
  resetState() {
888
1104
  this.set("root", null);
@@ -893,6 +1109,7 @@ var Editor = class extends BaseService {
893
1109
  this.set("stage", null);
894
1110
  this.set("highlightNode", null);
895
1111
  this.set("modifiedNodeIds", /* @__PURE__ */ new Map());
1112
+ this.set("invalidNodeIds", /* @__PURE__ */ new Map());
896
1113
  this.set("pageLength", 0);
897
1114
  }
898
1115
  destroy() {
@@ -903,6 +1120,51 @@ var Editor = class extends BaseService {
903
1120
  resetModifiedNodeId() {
904
1121
  this.get("modifiedNodeIds").clear();
905
1122
  }
1123
+ /**
1124
+ * 记录(或覆盖)某个节点在指定来源(属性表单 / 样式表单)上的校验错误信息。
1125
+ * @param id 节点 id
1126
+ * @param source 错误来源:'props'(属性表单)| 'style'(样式表单)
1127
+ * @param message 错误文案(可能是包含 <br> 的 HTML)
1128
+ */
1129
+ setInvalidNode(id, source, message) {
1130
+ const map = this.get("invalidNodeIds");
1131
+ const info = { ...map.get(id) || {} };
1132
+ info[source] = message;
1133
+ map.set(id, info);
1134
+ this.emit("invalid-node-change", map);
1135
+ }
1136
+ /**
1137
+ * 删除节点的校验错误记录。
1138
+ * @param id 节点 id
1139
+ * @param source 指定来源则仅删除该来源;不传则删除该节点全部来源的错误
1140
+ */
1141
+ deleteInvalidNode(id, source) {
1142
+ const map = this.get("invalidNodeIds");
1143
+ if (!map.has(id)) return;
1144
+ if (!source) map.delete(id);
1145
+ else {
1146
+ const info = { ...map.get(id) || {} };
1147
+ delete info[source];
1148
+ if (info.props || info.style) map.set(id, info);
1149
+ else map.delete(id);
1150
+ }
1151
+ this.emit("invalid-node-change", map);
1152
+ }
1153
+ /** 获取当前存在校验错误的节点错误 Map(key 为节点 id) */
1154
+ getInvalidNodeIds() {
1155
+ return this.get("invalidNodeIds");
1156
+ }
1157
+ /** 获取指定节点的校验错误信息 */
1158
+ getInvalidNodeInfo(id) {
1159
+ return this.get("invalidNodeIds").get(id);
1160
+ }
1161
+ /** 清空全部校验错误记录 */
1162
+ resetInvalidNodeId() {
1163
+ const map = this.get("invalidNodeIds");
1164
+ if (map.size === 0) return;
1165
+ map.clear();
1166
+ this.emit("invalid-node-change", map);
1167
+ }
906
1168
  usePlugin(options) {
907
1169
  super.usePlugin(options);
908
1170
  }
@@ -915,99 +1177,248 @@ var Editor = class extends BaseService {
915
1177
  emit(eventName, ...args) {
916
1178
  return super.emit(eventName, ...args);
917
1179
  }
1180
+ /**
1181
+ * 应用一次属性面板提交携带的校验错误信息(在写入历史记录之前调用,使历史快照与本次变更对齐)。
1182
+ * - invalidInfo 为空(调用方未携带):认为本次更新对应的节点已无校验错误,清除其全部来源的错误;
1183
+ * - error 非空则记录错误,为空则清除对应来源的错误。
1184
+ */
1185
+ applyInvalidInfo(config, invalidInfo) {
1186
+ if (!invalidInfo) {
1187
+ (Array.isArray(config) ? config : [config]).map((node) => node.id).filter((id) => id !== void 0).forEach((id) => this.deleteInvalidNode(id));
1188
+ return;
1189
+ }
1190
+ const { id, source, error } = invalidInfo;
1191
+ if (error) this.setInvalidNode(id, source, error);
1192
+ else this.deleteInvalidNode(id, source);
1193
+ }
1194
+ /** 删除被移除节点(含其子树)的校验错误记录,避免残留误报 */
1195
+ removeInvalidNodesBySubtree(nodes) {
1196
+ const map = this.get("invalidNodeIds");
1197
+ if (map.size === 0) return;
1198
+ let changed = false;
1199
+ nodes.forEach((node) => {
1200
+ traverseNode(node, (n) => {
1201
+ if (n.id !== void 0 && map.delete(n.id)) changed = true;
1202
+ });
1203
+ });
1204
+ if (changed) this.emit("invalid-node-change", map);
1205
+ }
1206
+ /** 清理不在当前 DSL 中的失效节点错误记录(用于整体替换 root 后) */
1207
+ pruneInvalidNodeIds() {
1208
+ const map = this.get("invalidNodeIds");
1209
+ if (map.size === 0) return;
1210
+ let changed = false;
1211
+ for (const id of [...map.keys()]) if (!this.getNodeById(id, false)) {
1212
+ map.delete(id);
1213
+ changed = true;
1214
+ }
1215
+ if (changed) this.emit("invalid-node-change", map);
1216
+ }
918
1217
  addModifiedNodeId(id) {
919
1218
  this.get("modifiedNodeIds").set(id, id);
920
1219
  }
1220
+ /**
1221
+ * 获取指定节点所属的页面 / 页面片:
1222
+ * - 普通节点返回其所在的 page;
1223
+ * - 节点本身就是 page / pageFragment 时返回它自己;
1224
+ * - 找不到时返回 null。
1225
+ * 供 `change` 事件携带「变更节点对应的 page」(而非编辑器当前选中页)。
1226
+ */
1227
+ getPageOfNode(id) {
1228
+ const { node, page } = this.getNodeInfo(id, false);
1229
+ if (page) return page;
1230
+ if (node && isPageOrFragment(node)) return node;
1231
+ return null;
1232
+ }
921
1233
  captureSelectionBeforeOp() {
922
1234
  if (this.selectionBeforeOp) return;
923
1235
  this.selectionBeforeOp = this.get("nodes").map((n) => n.id);
1236
+ this.invalidNodeIdsBeforeOp = new Map(this.get("invalidNodeIds"));
924
1237
  }
925
- pushOpHistory(opType, extra, pageData, historyDescription) {
1238
+ /**
1239
+ * 比较「上一次 root」与「新 root」的页面 / 页面片,按页面粒度把整体替换拆成历史记录:
1240
+ * - 新旧都存在且内容变化的页面 → 一条 `update`(整页快照替换,无 changeRecords);
1241
+ * - 仅新 root 存在的页面 → 一条 `add`;
1242
+ * - 仅旧 root 存在的页面 → 一条 `remove`。
1243
+ *
1244
+ * 每条记录落到对应页面自己的历史栈(与普通节点操作一致),并标记来源 `source`。
1245
+ * 内容未变化的页面不产生记录,避免重复设置相同 DSL 时产生噪声。
1246
+ */
1247
+ pushRootDiffHistory(preRoot, nextRoot, source) {
1248
+ const prevPages = preRoot?.items || [];
1249
+ const nextPages = nextRoot?.items || [];
1250
+ const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
1251
+ const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
1252
+ const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
1253
+ const addedPages = [];
1254
+ const removedPages = [];
1255
+ nextPages.forEach((nextPage) => {
1256
+ const prevPage = prevMap.get(`${nextPage.id}`);
1257
+ if (!prevPage) {
1258
+ this.pushPageDiffStep("add", nextPage, {
1259
+ newSchema: cloneDeep$1(toRaw(nextPage)),
1260
+ parentId: nextRoot.id,
1261
+ index: indexInItems(nextRoot, nextPage.id)
1262
+ }, source);
1263
+ addedPages.push(nextPage);
1264
+ } else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) this.pushPageDiffStep("update", nextPage, {
1265
+ oldSchema: cloneDeep$1(toRaw(prevPage)),
1266
+ newSchema: cloneDeep$1(toRaw(nextPage))
1267
+ }, source);
1268
+ });
1269
+ prevPages.forEach((prevPage) => {
1270
+ if (!nextMap.has(`${prevPage.id}`)) {
1271
+ this.pushPageDiffStep("remove", prevPage, {
1272
+ oldSchema: cloneDeep$1(toRaw(prevPage)),
1273
+ parentId: preRoot.id,
1274
+ index: indexInItems(preRoot, prevPage.id)
1275
+ }, source);
1276
+ removedPages.push(prevPage);
1277
+ }
1278
+ });
1279
+ if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
1280
+ add: addedPages,
1281
+ remove: removedPages
1282
+ });
1283
+ }
1284
+ /**
1285
+ * 构造一条页面级「set root」历史记录(不携带选区 / modifiedNodeIds 上下文)并落到该页面自己的栈。
1286
+ *
1287
+ * 连续 set root 替换:若该页栈最新一条已是**同来源**的 set root 记录({@link StepValue.rootStep} 且 `source` 相同),
1288
+ * 则用本次记录**替换**它而非新增,避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录;
1289
+ * 来源不同则照常新增(initial 基线不是 rootStep,不在此列)。
1290
+ */
1291
+ pushPageDiffStep(opType, page, diffItem, source) {
926
1292
  const step = {
1293
+ uuid: guid(),
1294
+ data: {
1295
+ name: page.name || "",
1296
+ id: page.id
1297
+ },
1298
+ opType,
1299
+ extra: {
1300
+ selectedBefore: [],
1301
+ selectedAfter: [],
1302
+ modifiedNodeIds: /* @__PURE__ */ new Map()
1303
+ },
1304
+ diff: [diffItem],
1305
+ rootStep: true
1306
+ };
1307
+ if (source) step.source = source;
1308
+ const top = history_default.getCurrentPageStep(page.id);
1309
+ if (top?.rootStep && top.source === source) history_default.replaceCurrentStep("page", step, page.id);
1310
+ else history_default.push("page", step, page.id);
1311
+ }
1312
+ pushOpHistory(opType, { diff, pageData, historyDescription, source }) {
1313
+ const step = {
1314
+ uuid: guid(),
927
1315
  data: pageData,
928
1316
  opType,
929
- selectedBefore: this.selectionBeforeOp ?? [],
930
- selectedAfter: this.get("nodes").map((n) => n.id),
931
- modifiedNodeIds: new Map(this.get("modifiedNodeIds")),
932
- ...extra
1317
+ extra: {
1318
+ selectedBefore: this.selectionBeforeOp ?? [],
1319
+ selectedAfter: this.get("nodes").map((n) => n.id),
1320
+ modifiedNodeIds: new Map(this.get("modifiedNodeIds")),
1321
+ invalidNodeIdsBefore: new Map(this.invalidNodeIdsBeforeOp ?? this.get("invalidNodeIds")),
1322
+ invalidNodeIdsAfter: new Map(this.get("invalidNodeIds"))
1323
+ },
1324
+ diff
933
1325
  };
934
1326
  if (historyDescription) step.historyDescription = historyDescription;
935
- history_default.push(step, pageData.id);
1327
+ if (source) step.source = source;
1328
+ const historyId = history_default.push("page", step, pageData.id) ? step.uuid : null;
1329
+ this.lastPushedHistoryId = historyId;
936
1330
  this.selectionBeforeOp = null;
1331
+ this.invalidNodeIdsBeforeOp = null;
1332
+ return historyId;
937
1333
  }
938
1334
  /**
939
1335
  * 应用历史操作(撤销 / 重做)
940
1336
  *
941
- * 所有 DSL 修改都走 `editor.add / remove / update`,并通过 `doNotPushHistory` 阻止再次入栈、
942
- * `doNotSelect / doNotSwitchPage` 让选区由方法末尾的统一逻辑兜底。
1337
+ * 删除 / 更新类修改走 `editor.remove / update`,并通过 `doNotPushHistory` 阻止再次入栈、
1338
+ * `doNotSelect / doNotSwitchPage` 让选区由方法末尾的统一逻辑兜底;
1339
+ * 「重新插回节点」(撤销 remove / 重做 add)需按 step 记录的 parentId / index 精确还原,
1340
+ * 不走 this.add,由手工 splice + stage.add 完成后补发等价的 add / change 事件(见 emitHistoryInsertEvents)。
943
1341
  *
944
- * 注意:这些公开方法会发出 add / remove / update 事件,业务侧若需要区分"用户操作"与"撤销重做触发",
1342
+ * 注意:这些路径都会发出 add / remove / update / change 事件,业务侧若需要区分"用户操作"与"撤销重做触发",
945
1343
  * 请监听 `history-change` 事件配合判断。
946
1344
  *
947
1345
  * @param step 操作记录
948
1346
  * @param reverse true = 撤销,false = 重做
949
1347
  */
950
1348
  async applyHistoryOp(step, reverse) {
1349
+ if (step.opType === "initial") return;
951
1350
  const root = this.get("root");
952
1351
  const stage = this.get("stage");
953
1352
  if (!root) return;
954
1353
  const commonOpts = {
955
1354
  doNotSelect: true,
956
1355
  doNotSwitchPage: true,
957
- doNotPushHistory: true
1356
+ doNotPushHistory: true,
1357
+ historySource: step.source
958
1358
  };
959
1359
  switch (step.opType) {
960
1360
  case "add": {
961
- const nodes = step.nodes ?? [];
962
- if (reverse) for (const n of nodes) {
963
- const existing = this.getNodeById(n.id, false);
1361
+ const items = step.diff ?? [];
1362
+ if (reverse) for (const { newSchema } of items) {
1363
+ if (!newSchema) continue;
1364
+ const existing = this.getNodeById(newSchema.id, false);
964
1365
  if (existing) await this.remove(existing, commonOpts);
965
1366
  }
966
1367
  else {
967
- const parent = this.getNodeById(step.parentId, false);
968
- if (parent) {
969
- const sorted = [...nodes].sort((a, b) => (step.indexMap?.[a.id] ?? 0) - (step.indexMap?.[b.id] ?? 0));
970
- for (const n of sorted) {
971
- const idx = step.indexMap?.[n.id];
972
- if (parent.items) {
973
- if (typeof idx === "number" && idx >= 0 && idx < parent.items.length) parent.items.splice(idx, 0, cloneDeep$1(n));
974
- else parent.items.push(cloneDeep$1(n));
975
- await stage?.add({
976
- config: cloneDeep$1(n),
977
- parent: cloneDeep$1(parent),
978
- parentId: parent.id,
979
- root: cloneDeep$1(root)
980
- });
981
- }
1368
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
1369
+ const addedNodes = [];
1370
+ for (const { newSchema, parentId, index } of sorted) {
1371
+ if (!newSchema || parentId === void 0) continue;
1372
+ const parent = this.getNodeById(parentId, false);
1373
+ if (parent?.items) {
1374
+ const addedNode = cloneDeep$1(newSchema);
1375
+ if (typeof index === "number" && index >= 0 && index < parent.items.length) parent.items.splice(index, 0, addedNode);
1376
+ else parent.items.push(addedNode);
1377
+ addedNodes.push(addedNode);
1378
+ await stage?.add({
1379
+ config: cloneDeep$1(newSchema),
1380
+ parent: cloneDeep$1(parent),
1381
+ parentId: parent.id,
1382
+ root: cloneDeep$1(root)
1383
+ });
982
1384
  }
983
1385
  }
1386
+ this.emitHistoryInsertEvents(addedNodes, step.source);
984
1387
  }
985
1388
  break;
986
1389
  }
987
1390
  case "remove": {
988
- const items = step.removedItems ?? [];
1391
+ const items = step.diff ?? [];
989
1392
  if (reverse) {
990
- const sorted = [...items].sort((a, b) => a.index - b.index);
991
- for (const { node, parentId, index } of sorted) {
1393
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
1394
+ const addedNodes = [];
1395
+ for (const { oldSchema, parentId, index } of sorted) {
1396
+ if (!oldSchema || parentId === void 0) continue;
992
1397
  const parent = this.getNodeById(parentId, false);
993
1398
  if (parent?.items) {
994
- parent.items.splice(index, 0, cloneDeep$1(node));
1399
+ const addedNode = cloneDeep$1(oldSchema);
1400
+ parent.items.splice(index ?? parent.items.length, 0, addedNode);
1401
+ addedNodes.push(addedNode);
995
1402
  await stage?.add({
996
- config: cloneDeep$1(node),
1403
+ config: cloneDeep$1(oldSchema),
997
1404
  parent: cloneDeep$1(parent),
998
1405
  parentId,
999
1406
  root: cloneDeep$1(root)
1000
1407
  });
1001
1408
  }
1002
1409
  }
1003
- } else for (const { node } of items) {
1004
- const existing = this.getNodeById(node.id, false);
1410
+ this.emitHistoryInsertEvents(addedNodes, step.source);
1411
+ } else for (const { oldSchema } of items) {
1412
+ if (!oldSchema) continue;
1413
+ const existing = this.getNodeById(oldSchema.id, false);
1005
1414
  if (existing) await this.remove(existing, commonOpts);
1006
1415
  }
1007
1416
  break;
1008
1417
  }
1009
1418
  case "update": {
1010
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
1419
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
1420
+ const oldNode = oldSchema;
1421
+ const newNode = newSchema;
1011
1422
  if (changeRecords?.length) {
1012
1423
  const sourceForValues = reverse ? oldNode : newNode;
1013
1424
  const patch = {
@@ -1023,14 +1434,20 @@ var Editor = class extends BaseService {
1023
1434
  }
1024
1435
  return cloneDeep$1(reverse ? oldNode : newNode);
1025
1436
  });
1026
- if (configs.length) await this.update(configs, { doNotPushHistory: true });
1437
+ if (configs.length) await this.update(configs, {
1438
+ doNotPushHistory: true,
1439
+ historySource: step.source
1440
+ });
1027
1441
  break;
1028
1442
  }
1029
1443
  }
1030
- this.set("modifiedNodeIds", step.modifiedNodeIds);
1444
+ this.set("modifiedNodeIds", step.extra?.modifiedNodeIds ?? /* @__PURE__ */ new Map());
1445
+ const invalidToRestore = (reverse ? step.extra?.invalidNodeIdsBefore : step.extra?.invalidNodeIdsAfter) ?? /* @__PURE__ */ new Map();
1446
+ this.set("invalidNodeIds", new Map(invalidToRestore));
1447
+ this.emit("invalid-node-change", this.get("invalidNodeIds"));
1031
1448
  const page = toRaw(this.get("page"));
1032
1449
  if (page) {
1033
- const selectIds = reverse ? step.selectedBefore : step.selectedAfter;
1450
+ const selectIds = (reverse ? step.extra?.selectedBefore : step.extra?.selectedAfter) ?? [];
1034
1451
  setTimeout(() => {
1035
1452
  if (!selectIds.length) return;
1036
1453
  if (selectIds.length > 1) {
@@ -1044,6 +1461,23 @@ var Editor = class extends BaseService {
1044
1461
  selectedConfigExceptionHandler(config) {
1045
1462
  return resolveSelectedNode(config, (id) => this.getNodeInfo(id), this.state.root?.id);
1046
1463
  }
1464
+ /**
1465
+ * 撤销 remove / 重做 add 通过手工 splice 按 step 记录的 parentId / index 精确还原节点(不走 this.add,原因见调用处注释),
1466
+ * 这里补齐与 this.add 等价的 add / change 事件,保证撤销/重做路径的事件行为与正向操作一致。
1467
+ */
1468
+ emitHistoryInsertEvents(nodes, source) {
1469
+ if (!nodes.length) return;
1470
+ this.emit("add", nodes);
1471
+ this.emit("change", {
1472
+ type: "add",
1473
+ data: nodes.map((node) => ({
1474
+ node,
1475
+ page: this.getPageOfNode(node.id)
1476
+ })),
1477
+ historySource: source,
1478
+ doNotPushHistory: true
1479
+ });
1480
+ }
1047
1481
  };
1048
1482
  var editor_default = new Editor();
1049
1483
  //#endregion