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

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 (119) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +11 -2
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
  4. package/dist/es/components/CompareForm.js +5 -0
  5. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +167 -0
  6. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
  7. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
  8. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
  9. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  10. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +2 -2
  11. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
  12. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
  13. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
  14. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
  15. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  16. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
  17. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
  18. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
  19. package/dist/es/hooks/use-code-block-edit.js +2 -2
  20. package/dist/es/hooks/use-stage.js +15 -7
  21. package/dist/es/index.js +3 -1
  22. package/dist/es/initService.js +6 -6
  23. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +4 -0
  24. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
  25. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
  26. package/dist/es/layouts/history-list/Bucket.js +5 -0
  27. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +84 -0
  28. package/dist/es/layouts/history-list/CodeBlockTab.js +5 -0
  29. package/dist/es/layouts/history-list/CodeBlockTab.vue_vue_type_script_setup_true_lang.js +62 -0
  30. package/dist/es/layouts/history-list/DataSourceTab.js +5 -0
  31. package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +62 -0
  32. package/dist/es/layouts/history-list/GroupRow.js +5 -0
  33. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +180 -0
  34. package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
  35. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +195 -0
  36. package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
  37. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +270 -0
  38. package/dist/es/layouts/history-list/InitialRow.js +5 -0
  39. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +48 -0
  40. package/dist/es/layouts/history-list/PageTab.js +5 -0
  41. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +99 -0
  42. package/dist/es/layouts/history-list/composables.js +154 -0
  43. package/dist/es/layouts/sidebar/layer/use-node-status.js +1 -1
  44. package/dist/es/services/BaseService.js +31 -68
  45. package/dist/es/services/codeBlock.js +205 -8
  46. package/dist/es/services/componentList.js +2 -2
  47. package/dist/es/services/dataSource.js +217 -8
  48. package/dist/es/services/dep.js +21 -9
  49. package/dist/es/services/editor.js +350 -160
  50. package/dist/es/services/events.js +2 -2
  51. package/dist/es/services/history.js +407 -12
  52. package/dist/es/services/keybinding.js +2 -2
  53. package/dist/es/services/props.js +8 -8
  54. package/dist/es/services/stageOverlay.js +2 -2
  55. package/dist/es/services/storage.js +2 -2
  56. package/dist/es/services/ui.js +2 -2
  57. package/dist/es/style.css +347 -1
  58. package/dist/es/utils/code-block.js +122 -0
  59. package/dist/es/utils/content-menu.js +4 -5
  60. package/dist/es/utils/dep/worker.js +1 -1
  61. package/dist/es/utils/editor.js +2 -2
  62. package/dist/es/utils/undo-redo.js +20 -0
  63. package/dist/style.css +347 -1
  64. package/dist/tmagic-editor.umd.cjs +3165 -774
  65. package/package.json +7 -7
  66. package/src/Editor.vue +7 -0
  67. package/src/components/CodeBlockEditor.vue +20 -109
  68. package/src/components/CodeParams.vue +6 -0
  69. package/src/components/CompareForm.vue +225 -0
  70. package/src/editorProps.ts +2 -0
  71. package/src/fields/Code.vue +19 -3
  72. package/src/fields/CodeSelect.vue +17 -1
  73. package/src/fields/CodeSelectCol.vue +30 -2
  74. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
  75. package/src/fields/DataSourceFieldSelect/Index.vue +2 -2
  76. package/src/fields/DataSourceFields.vue +12 -3
  77. package/src/fields/DataSourceMethodSelect.vue +4 -1
  78. package/src/fields/DataSourceMethods.vue +14 -4
  79. package/src/fields/DataSourceMocks.vue +13 -3
  80. package/src/fields/DataSourceSelect.vue +4 -1
  81. package/src/fields/EventSelect.vue +57 -8
  82. package/src/fields/KeyValue.vue +17 -4
  83. package/src/fields/UISelect.vue +5 -2
  84. package/src/hooks/use-code-block-edit.ts +5 -2
  85. package/src/hooks/use-stage.ts +23 -7
  86. package/src/index.ts +1 -0
  87. package/src/layouts/CodeEditor.vue +15 -0
  88. package/src/layouts/Framework.vue +10 -5
  89. package/src/layouts/NavMenu.vue +9 -0
  90. package/src/layouts/history-list/Bucket.vue +99 -0
  91. package/src/layouts/history-list/CodeBlockTab.vue +61 -0
  92. package/src/layouts/history-list/DataSourceTab.vue +61 -0
  93. package/src/layouts/history-list/GroupRow.vue +207 -0
  94. package/src/layouts/history-list/HistoryDiffDialog.vue +212 -0
  95. package/src/layouts/history-list/HistoryListPanel.vue +271 -0
  96. package/src/layouts/history-list/InitialRow.vue +41 -0
  97. package/src/layouts/history-list/PageTab.vue +94 -0
  98. package/src/layouts/history-list/composables.ts +195 -0
  99. package/src/layouts/sidebar/layer/use-node-status.ts +9 -6
  100. package/src/services/BaseService.ts +35 -86
  101. package/src/services/codeBlock.ts +256 -5
  102. package/src/services/dataSource.ts +257 -6
  103. package/src/services/dep.ts +32 -9
  104. package/src/services/editor.ts +454 -171
  105. package/src/services/history.ts +452 -8
  106. package/src/services/props.ts +2 -2
  107. package/src/theme/history-list-panel.scss +423 -0
  108. package/src/theme/theme.scss +1 -0
  109. package/src/type.ts +179 -6
  110. package/src/utils/code-block.ts +150 -0
  111. package/src/utils/content-menu.ts +8 -5
  112. package/src/utils/dep/worker.ts +5 -3
  113. package/src/utils/index.ts +1 -0
  114. package/src/utils/undo-redo.ts +23 -0
  115. package/types/index.d.ts +2105 -810
  116. package/dist/es/utils/compose.js +0 -44
  117. package/dist/es/utils/editor-history.js +0 -100
  118. package/src/utils/compose.ts +0 -52
  119. package/src/utils/editor-history.ts +0 -138
@@ -0,0 +1,423 @@
1
+ .m-editor-history-list-popover {
2
+ padding: 0 !important;
3
+
4
+ .m-editor-history-list {
5
+ position: relative;
6
+ padding: 4px 8px 8px;
7
+ }
8
+
9
+ // 关闭按钮悬浮在 tab 标题同一行(绝对定位,不占布局空间)。
10
+ // top 对齐容器顶部内边距,height 跟 el-tabs/t-tabs 头部默认高度一致,
11
+ // 再用 flex 居中让图标与 tab 标题视觉对齐。
12
+ .m-editor-history-list-close {
13
+ position: absolute;
14
+ top: 4px;
15
+ right: 4px;
16
+ z-index: 1;
17
+ display: flex;
18
+ align-items: center;
19
+ height: 40px;
20
+ margin: 0;
21
+ color: #909399;
22
+
23
+ &:hover {
24
+ color: #409eff;
25
+ }
26
+ }
27
+
28
+ .m-editor-history-list-tabs {
29
+ .el-tabs__header,
30
+ .t-tabs__header {
31
+ margin-bottom: 4px;
32
+ }
33
+ }
34
+
35
+ .m-editor-history-list-empty {
36
+ padding: 24px 0;
37
+ text-align: center;
38
+ color: #909399;
39
+ font-size: 12px;
40
+ }
41
+
42
+ .m-editor-history-list-ul {
43
+ margin: 0;
44
+ padding: 0;
45
+ list-style: none;
46
+ }
47
+
48
+ .m-editor-history-list-item {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 6px;
52
+ padding: 6px 8px;
53
+ border-radius: 4px;
54
+ font-size: 12px;
55
+ line-height: 1.4;
56
+ color: #303133;
57
+ cursor: default;
58
+
59
+ // 合并组(卡片)自己定义了 hover/active 视觉,跳过通用单步 hover 避免色彩叠加
60
+ &:not(.m-editor-history-list-group.is-merged):hover {
61
+ background-color: rgba(0, 0, 0, 0.04);
62
+ }
63
+
64
+ &.is-undone {
65
+ color: #c0c4cc;
66
+
67
+ .m-editor-history-list-item-op {
68
+ opacity: 0.5;
69
+ }
70
+ }
71
+
72
+ // 当前所在的「单步」记录:左侧蓝条 + 浅蓝底;
73
+ // 合并组的当前态由 `.is-merged.is-current` 单独覆盖(border-left + 卡片背景),
74
+ // 故这里仅作用于「非合并组」的单步条目,避免与卡片样式互相干扰。
75
+ &.is-current:not(.m-editor-history-list-group.is-merged) {
76
+ background-color: rgba(64, 158, 255, 0.1);
77
+ box-shadow: inset 2px 0 0 #409eff;
78
+
79
+ &:hover {
80
+ background-color: rgba(64, 158, 255, 0.16);
81
+ }
82
+
83
+ .m-editor-history-list-item-desc {
84
+ font-weight: 600;
85
+ color: #409eff;
86
+ }
87
+ }
88
+ }
89
+
90
+ .m-editor-history-list-group {
91
+ flex-direction: column;
92
+ align-items: stretch;
93
+ gap: 0;
94
+
95
+ .m-editor-history-list-group-head {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 6px;
99
+ cursor: default;
100
+
101
+ &.is-clickable {
102
+ cursor: pointer;
103
+ }
104
+ }
105
+
106
+ .m-editor-history-list-group-toggle {
107
+ flex: 0 0 auto;
108
+ width: 16px;
109
+ text-align: center;
110
+ color: #909399;
111
+ font-size: 12px;
112
+ user-select: none;
113
+ transition: transform 0.15s ease;
114
+ pointer-events: none;
115
+
116
+ &.is-expanded {
117
+ transform: rotate(180deg);
118
+ }
119
+ }
120
+
121
+ // 合并组(≥2 个连续同节点的修改被聚合)需要与单步条目明显区分:
122
+ // - 左侧 3px 紫色色条暗示「这是一组」;
123
+ // - 浅紫背景 + 边框把整个组视觉化为一张「卡片」;
124
+ // - 头部加 padding,避免和单步条目混在一起难以辨认。
125
+ &.is-merged {
126
+ margin: 4px 0;
127
+ padding: 4px 8px 6px;
128
+ background-color: rgba(144, 105, 219, 0.06);
129
+ border: 1px solid rgba(144, 105, 219, 0.18);
130
+ border-left: 3px solid #9069db;
131
+ border-radius: 4px;
132
+
133
+ // 卡片本体已经有背景色,hover 状态以更深的同色提示交互
134
+ &:hover {
135
+ background-color: rgba(144, 105, 219, 0.1);
136
+ }
137
+
138
+ .m-editor-history-list-group-head {
139
+ font-weight: 600;
140
+ color: #5b3fa5;
141
+ }
142
+
143
+ // 已撤销态:整张卡片去色
144
+ &.is-undone {
145
+ background-color: rgba(192, 196, 204, 0.08);
146
+ border-color: rgba(192, 196, 204, 0.4);
147
+ border-left-color: #c0c4cc;
148
+
149
+ .m-editor-history-list-group-head {
150
+ color: #c0c4cc;
151
+ }
152
+ }
153
+
154
+ // 当前组:卡片左条变蓝,与单步「当前」高亮一致
155
+ &.is-current {
156
+ background-color: rgba(64, 158, 255, 0.08);
157
+ border-color: rgba(64, 158, 255, 0.3);
158
+ border-left-color: #409eff;
159
+ box-shadow: none; // 覆盖 .is-current 公共的 inset 阴影
160
+
161
+ .m-editor-history-list-group-head {
162
+ color: #409eff;
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ .m-editor-history-list-substeps {
169
+ margin: 6px 0 0 6px;
170
+ padding: 0;
171
+ list-style: none;
172
+ border-left: 1px dashed rgba(144, 105, 219, 0.45);
173
+
174
+ li {
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 6px;
178
+ padding: 3px 8px;
179
+ font-size: 11px;
180
+ color: #606266;
181
+ cursor: default;
182
+ border-radius: 3px;
183
+
184
+ &.is-clickable {
185
+ cursor: pointer;
186
+
187
+ &:hover {
188
+ background-color: rgba(144, 105, 219, 0.1);
189
+ }
190
+ }
191
+
192
+ &.is-undone {
193
+ color: #c0c4cc;
194
+ }
195
+
196
+ &.is-current {
197
+ color: #409eff;
198
+ font-weight: 600;
199
+ background-color: rgba(64, 158, 255, 0.08);
200
+ border-radius: 3px;
201
+ }
202
+ }
203
+ }
204
+
205
+ .m-editor-history-list-item-current {
206
+ flex: 0 0 auto;
207
+ padding: 0 6px;
208
+ border-radius: 2px;
209
+ font-size: 10px;
210
+ line-height: 16px;
211
+ color: #fff;
212
+ background-color: #409eff;
213
+ font-weight: 500;
214
+ }
215
+
216
+ .m-editor-history-list-item-index {
217
+ flex: 0 0 auto;
218
+ color: #909399;
219
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
220
+ font-size: 11px;
221
+ font-weight: 400; // 防止被合并组头部的粗体继承
222
+ white-space: nowrap;
223
+ }
224
+
225
+ .m-editor-history-list-item-op {
226
+ flex: 0 0 auto;
227
+ padding: 0 6px;
228
+ border-radius: 2px;
229
+ font-size: 11px;
230
+ line-height: 18px;
231
+ color: #fff;
232
+ background-color: #909399;
233
+
234
+ &.op-add {
235
+ background-color: #67c23a;
236
+ }
237
+
238
+ &.op-remove {
239
+ background-color: #f56c6c;
240
+ }
241
+
242
+ &.op-update {
243
+ background-color: #409eff;
244
+ }
245
+
246
+ &.op-initial {
247
+ background-color: #909399;
248
+ }
249
+ }
250
+
251
+ .m-editor-history-list-initial {
252
+ cursor: default;
253
+ color: #606266;
254
+ border-top: 1px dashed #dcdfe6;
255
+ margin-top: 4px;
256
+ padding-top: 8px;
257
+
258
+ &.is-clickable {
259
+ cursor: pointer;
260
+ }
261
+
262
+ .m-editor-history-list-item-desc {
263
+ font-style: italic;
264
+ }
265
+ }
266
+
267
+ .m-editor-history-list-item-desc {
268
+ flex: 1 1 auto;
269
+ overflow: hidden;
270
+ text-overflow: ellipsis;
271
+ white-space: nowrap;
272
+ }
273
+
274
+ // 「合并 N 步」徽标:紫色实心胶囊,与合并组卡片色系一致,醒目区分单步条目。
275
+ .m-editor-history-list-item-merge {
276
+ flex: 0 0 auto;
277
+ padding: 0 8px;
278
+ border-radius: 8px;
279
+ font-size: 10px;
280
+ line-height: 16px;
281
+ color: #fff;
282
+ background-color: #9069db;
283
+ font-weight: 500;
284
+ letter-spacing: 0.2px;
285
+ }
286
+
287
+ .m-editor-history-list-item-diff {
288
+ flex: 0 0 auto;
289
+ padding: 0 6px;
290
+ border-radius: 2px;
291
+ font-size: 10px;
292
+ line-height: 16px;
293
+ color: #409eff;
294
+ background-color: rgba(64, 158, 255, 0.1);
295
+ cursor: pointer;
296
+ user-select: none;
297
+
298
+ &:hover {
299
+ background-color: rgba(64, 158, 255, 0.2);
300
+ }
301
+ }
302
+
303
+ // 「回滚」按钮:类 git revert,把目标 step 反向应用一次作为新提交。
304
+ // 使用与「查看差异」不同的色调(橙黄),用来区分"可逆操作"与"只读对比"。
305
+ .m-editor-history-list-item-revert {
306
+ flex: 0 0 auto;
307
+ padding: 0 6px;
308
+ border-radius: 2px;
309
+ font-size: 10px;
310
+ line-height: 16px;
311
+ color: #e6a23c;
312
+ background-color: rgba(230, 162, 60, 0.12);
313
+ cursor: pointer;
314
+ user-select: none;
315
+
316
+ &:hover {
317
+ background-color: rgba(230, 162, 60, 0.25);
318
+ }
319
+ }
320
+
321
+ .m-editor-history-list-substep-desc {
322
+ flex: 1 1 auto;
323
+ overflow: hidden;
324
+ text-overflow: ellipsis;
325
+ white-space: nowrap;
326
+ }
327
+
328
+ .m-editor-history-list-bucket {
329
+ margin-bottom: 8px;
330
+
331
+ &:last-child {
332
+ margin-bottom: 0;
333
+ }
334
+ }
335
+
336
+ .m-editor-history-list-bucket-title {
337
+ display: flex;
338
+ align-items: center;
339
+ gap: 6px;
340
+ padding: 4px 8px;
341
+ font-size: 12px;
342
+ font-weight: 500;
343
+ color: #606266;
344
+ background-color: #f5f7fa;
345
+ border-radius: 4px;
346
+
347
+ code {
348
+ flex: 1 1 auto;
349
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
350
+ font-size: 11px;
351
+ color: #409eff;
352
+ overflow: hidden;
353
+ text-overflow: ellipsis;
354
+ white-space: nowrap;
355
+ }
356
+ }
357
+
358
+ .m-editor-history-list-bucket-count {
359
+ flex: 0 0 auto;
360
+ color: #909399;
361
+ font-weight: 400;
362
+ font-size: 11px;
363
+ }
364
+ }
365
+
366
+ .m-editor-history-diff-dialog {
367
+ .m-editor-history-diff-dialog-body {
368
+ display: flex;
369
+ flex-direction: column;
370
+ }
371
+
372
+ .m-editor-history-diff-dialog-header {
373
+ display: flex;
374
+ align-items: center;
375
+ justify-content: space-between;
376
+ margin-bottom: 8px;
377
+ padding: 8px 12px;
378
+ background-color: #f5f7fa;
379
+ border-radius: 4px;
380
+ gap: 12px;
381
+ }
382
+
383
+ .m-editor-history-diff-dialog-controls {
384
+ display: flex;
385
+ align-items: center;
386
+ flex: 0 0 auto;
387
+ gap: 8px;
388
+ }
389
+
390
+ .m-editor-history-diff-dialog-view,
391
+ .m-editor-history-diff-dialog-mode {
392
+ flex: 0 0 auto;
393
+ }
394
+
395
+ .m-editor-history-diff-dialog-target {
396
+ flex: 1 1 auto;
397
+ font-size: 13px;
398
+ font-weight: 500;
399
+ color: #303133;
400
+ overflow: hidden;
401
+ text-overflow: ellipsis;
402
+ white-space: nowrap;
403
+ }
404
+
405
+ .m-editor-history-diff-dialog-legend {
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 6px;
409
+ margin-bottom: 12px;
410
+ padding: 0 4px;
411
+ }
412
+
413
+ .m-editor-history-diff-dialog-arrow {
414
+ color: #909399;
415
+ font-size: 12px;
416
+ }
417
+
418
+ .m-editor-history-diff-dialog-tip {
419
+ margin-left: 8px;
420
+ color: #e6a23c;
421
+ font-size: 12px;
422
+ }
423
+ }
@@ -1,5 +1,6 @@
1
1
  @use "./search-input.scss";
2
2
  @use "./nav-menu.scss";
3
+ @use "./history-list-panel.scss";
3
4
  @use "./framework.scss";
4
5
  @use "./sidebar.scss";
5
6
  @use "./layer-panel.scss";
package/src/type.ts CHANGED
@@ -22,7 +22,17 @@ import type * as Monaco from 'monaco-editor';
22
22
  import type { default as Sortable, Options, SortableEvent } from 'sortablejs';
23
23
  import type { PascalCasedProperties, Writable } from 'type-fest';
24
24
 
25
- import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/core';
25
+ import type {
26
+ CodeBlockContent,
27
+ CodeBlockDSL,
28
+ DataSourceSchema,
29
+ Id,
30
+ MApp,
31
+ MContainer,
32
+ MNode,
33
+ MPage,
34
+ MPageFragment,
35
+ } from '@tmagic/core';
26
36
  import type { ChangeRecord, FormConfig, TableColumnConfig, TypeFunction } from '@tmagic/form';
27
37
  import type StageCore from '@tmagic/stage';
28
38
  import type {
@@ -138,7 +148,7 @@ export interface EditorInstallOptions {
138
148
  customCreateMonacoDiffEditor: (
139
149
  monaco: typeof import('monaco-editor'),
140
150
  codeEditorEl: HTMLElement,
141
- options: Monaco.editor.IStandaloneEditorConstructionOptions & { editorCustomType?: string },
151
+ options: Monaco.editor.IStandaloneDiffEditorConstructionOptions & { editorCustomType?: string },
142
152
  ) => Promise<Monaco.editor.IStandaloneDiffEditor> | Monaco.editor.IStandaloneDiffEditor;
143
153
  [key: string]: any;
144
154
  }
@@ -407,6 +417,7 @@ export interface MenuComponent {
407
417
  * 'rule': 显示隐藏标尺
408
418
  * 'scale-to-original': 缩放到实际大小
409
419
  * 'scale-to-fit': 缩放以适应
420
+ * 'history-list': 历史记录面板(按 页面 / 数据源 / 代码块 三个 tab 展示,相邻同目标修改自动合并)
410
421
  */
411
422
  // #region MenuItem
412
423
  export type MenuItem =
@@ -421,6 +432,7 @@ export type MenuItem =
421
432
  | 'rule'
422
433
  | 'scale-to-original'
423
434
  | 'scale-to-fit'
435
+ | 'history-list'
424
436
  | MenuButton
425
437
  | MenuComponent
426
438
  | string;
@@ -627,18 +639,161 @@ export interface StepValue {
627
639
  indexMap?: Record<string, number>;
628
640
  /** opType 'remove': 被删除的节点及其位置信息 */
629
641
  removedItems?: { node: MNode; parentId: Id; index: number }[];
630
- /** opType 'update': 变更前后的节点快照 */
631
- updatedItems?: { oldNode: MNode; newNode: MNode }[];
642
+ /**
643
+ * opType 'update': 变更前后的节点快照
644
+ *
645
+ * `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
646
+ * 缺省(未传 / 空数组)才退化为整节点替换。
647
+ */
648
+ updatedItems?: { oldNode: MNode; newNode: MNode; changeRecords?: ChangeRecord[] }[];
649
+ /**
650
+ * 调用方可选传入的人类可读描述(如「调整按钮颜色」),用于历史面板展示。
651
+ * 不影响 undo/redo 行为;缺省时面板会根据节点 / propPath 自动生成描述。
652
+ */
653
+ historyDescription?: string;
632
654
  }
633
655
  // #endregion StepValue
634
656
 
657
+ // #region CodeBlockStepValue
658
+ /**
659
+ * 代码块历史记录条目。按 codeBlock.id 分组保存到 historyState.codeBlockState。
660
+ * - 新增:oldContent = null,newContent = 新内容
661
+ * - 更新:oldContent / newContent 都为对应内容
662
+ * - 删除:newContent = null,oldContent = 删除前内容
663
+ */
664
+ export interface CodeBlockStepValue {
665
+ /** 关联的代码块 id */
666
+ id: Id;
667
+ /** 变更前的代码块内容,新增时为 null */
668
+ oldContent: CodeBlockContent | null;
669
+ /** 变更后的代码块内容,删除时为 null */
670
+ newContent: CodeBlockContent | null;
671
+ /**
672
+ * form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
673
+ * 缺省才退化为整内容替换。新增/删除场景通常无 changeRecords。
674
+ */
675
+ changeRecords?: ChangeRecord[];
676
+ /** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
677
+ historyDescription?: string;
678
+ }
679
+ // #endregion CodeBlockStepValue
680
+
681
+ // #region DataSourceStepValue
682
+ /**
683
+ * 数据源历史记录条目。按 dataSource.id 分组保存到 historyState.dataSourceState。
684
+ * - 新增:oldSchema = null,newSchema = 新 schema
685
+ * - 更新:oldSchema / newSchema 都为对应 schema
686
+ * - 删除:newSchema = null,oldSchema = 删除前 schema
687
+ */
688
+ export interface DataSourceStepValue {
689
+ /** 关联的数据源 id */
690
+ id: Id;
691
+ /** 变更前的数据源 schema,新增时为 null */
692
+ oldSchema: DataSourceSchema | null;
693
+ /** 变更后的数据源 schema,删除时为 null */
694
+ newSchema: DataSourceSchema | null;
695
+ /**
696
+ * form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
697
+ * 缺省才退化为整 schema 替换。新增/删除场景通常无 changeRecords。
698
+ */
699
+ changeRecords?: ChangeRecord[];
700
+ /** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
701
+ historyDescription?: string;
702
+ }
703
+ // #endregion DataSourceStepValue
704
+
635
705
  export interface HistoryState {
636
706
  pageId?: Id;
637
707
  pageSteps: Record<Id, UndoRedo<StepValue>>;
638
708
  canRedo: boolean;
639
709
  canUndo: boolean;
710
+ /**
711
+ * 代码块历史栈,按 codeBlock.id 分组(每个代码块独立一份 UndoRedo)。
712
+ * 与页面/节点无关,支持独立 undo/redo。
713
+ */
714
+ codeBlockState: Record<Id, UndoRedo<CodeBlockStepValue>>;
715
+ /**
716
+ * 数据源历史栈,按 dataSource.id 分组(每个数据源独立一份 UndoRedo)。
717
+ * 与页面/节点无关,支持独立 undo/redo。
718
+ */
719
+ dataSourceState: Record<Id, UndoRedo<DataSourceStepValue>>;
640
720
  }
641
721
 
722
+ // #region HistoryListEntry
723
+ /**
724
+ * 历史面板用:当前页面的一条历史步骤(包含位置和是否已应用)。
725
+ */
726
+ export interface PageHistoryStepEntry {
727
+ /** 步骤内容 */
728
+ step: StepValue;
729
+ /** 在所属栈中的索引(0 为最早) */
730
+ index: number;
731
+ /** 是否处于"已应用"段(即位于栈游标之前)。撤销后变为 false。 */
732
+ applied: boolean;
733
+ /** 是否为当前所在的步骤(栈中最近一次已应用的那一步,即 index === cursor - 1)。 */
734
+ isCurrent?: boolean;
735
+ }
736
+
737
+ /**
738
+ * 页面历史面板分组。
739
+ * - 连续修改同一目标节点(updatedItems[0].oldNode.id 一致)的 'update' 步骤合并成一组;
740
+ * - 多节点更新 / add / remove 始终独立成组(无法明确归属单一目标)。
741
+ * - targetId 为 undefined 表示"无明确目标"(如 add/remove/多节点 update),不参与合并。
742
+ */
743
+ export interface PageHistoryGroup {
744
+ kind: 'page';
745
+ /** 所属页面 id */
746
+ pageId: Id;
747
+ /** 该分组的操作类型 */
748
+ opType: HistoryOpType;
749
+ /**
750
+ * 合并的目标节点 id;只有"单节点 update"才有值,并按此 id 与相邻同 id 的 update 合并。
751
+ * undefined 表示该分组不可被合并(add / remove / 多节点 update)。
752
+ */
753
+ targetId?: Id;
754
+ /** 目标节点的可读名(取最后一步的 newNode.name/type/id) */
755
+ targetName?: string;
756
+ /** 组内所有步骤,按时间正序 */
757
+ steps: PageHistoryStepEntry[];
758
+ /** 组内最后一步是否已应用 */
759
+ applied: boolean;
760
+ /** 是否为当前所在的分组(包含栈中最近一次已应用步骤的那一组)。 */
761
+ isCurrent?: boolean;
762
+ }
763
+
764
+ /**
765
+ * 代码块历史面板分组。
766
+ * - 同一 codeBlockId 的栈内,相邻的 'update' 操作会合并成一个 group;
767
+ * - 'add' / 'remove' 始终独立成组(语义上是一次性事件)。
768
+ */
769
+ export interface CodeBlockHistoryGroup {
770
+ kind: 'code-block';
771
+ /** 关联的 codeBlock id */
772
+ id: Id;
773
+ /** 该分组的操作类型 */
774
+ opType: HistoryOpType;
775
+ /** 组内所有步骤,按时间正序 */
776
+ steps: { step: CodeBlockStepValue; index: number; applied: boolean; isCurrent?: boolean }[];
777
+ /** 组内最后一步是否已应用,用于整组的状态展示 */
778
+ applied: boolean;
779
+ /** 是否为当前所在的分组(包含该栈最近一次已应用步骤的那一组)。 */
780
+ isCurrent?: boolean;
781
+ }
782
+
783
+ /**
784
+ * 数据源历史面板分组,结构同 CodeBlockHistoryGroup。
785
+ */
786
+ export interface DataSourceHistoryGroup {
787
+ kind: 'data-source';
788
+ id: Id;
789
+ opType: HistoryOpType;
790
+ steps: { step: DataSourceStepValue; index: number; applied: boolean; isCurrent?: boolean }[];
791
+ applied: boolean;
792
+ /** 是否为当前所在的分组(包含该栈最近一次已应用步骤的那一组)。 */
793
+ isCurrent?: boolean;
794
+ }
795
+ // #endregion HistoryListEntry
796
+
642
797
  export enum KeyBindingCommand {
643
798
  /** 复制 */
644
799
  COPY_NODE = 'tmagic-system-copy-node',
@@ -874,12 +1029,30 @@ export const canUsePluginMethods = {
874
1029
 
875
1030
  export type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
876
1031
 
1032
+ /**
1033
+ * 历史记录写入相关的通用配置(codeBlock / dataSource / editor 共用)
1034
+ * - doNotPushHistory: 操作完成后是否不要将本次操作压入历史栈(撤销/重做记录),默认 false
1035
+ * - historyDescription: 入栈时附带的人类可读描述,用于历史面板展示;不影响 undo/redo 行为,缺省时面板会自动生成描述
1036
+ */
1037
+ export interface HistoryOpOptions {
1038
+ doNotPushHistory?: boolean;
1039
+ historyDescription?: string;
1040
+ }
1041
+
1042
+ /**
1043
+ * 在 HistoryOpOptions 基础上携带 form 端 propPath/value 变更记录,
1044
+ * 用于历史记录的精细化撤销/重做(按 propPath 局部 patch)。
1045
+ */
1046
+ export interface HistoryOpOptionsWithChangeRecords extends HistoryOpOptions {
1047
+ changeRecords?: ChangeRecord[];
1048
+ }
1049
+
877
1050
  /**
878
1051
  * DSL 修改类操作的通用配置
879
1052
  * - doNotSelect: 操作后是否不要自动触发选中(不调用 this.select / this.multiSelect / stage.select / stage.multiSelect)
880
1053
  * - doNotSwitchPage: 操作若会引发当前页面切换(如新增 / 删除 / 跨页移动),是否跳过这次切换
881
1054
  */
882
- export type DslOpOptions = {
1055
+ export interface DslOpOptions extends HistoryOpOptions {
883
1056
  doNotSelect?: boolean;
884
1057
  doNotSwitchPage?: boolean;
885
- };
1058
+ }