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

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 (84) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +9 -0
  2. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +46 -28
  3. package/dist/es/editorProps.js +2 -0
  4. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
  5. package/dist/es/hooks/use-code-block-edit.js +6 -3
  6. package/dist/es/hooks/use-data-source-edit.js +5 -2
  7. package/dist/es/hooks/use-stage.js +8 -4
  8. package/dist/es/index.js +3 -1
  9. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +2 -5
  10. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  11. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +37 -14
  12. package/dist/es/layouts/history-list/BucketTab.js +5 -0
  13. package/dist/es/layouts/history-list/{CodeBlockTab.vue_vue_type_script_setup_true_lang.js → BucketTab.vue_vue_type_script_setup_true_lang.js} +30 -16
  14. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +91 -60
  15. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +82 -30
  16. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +140 -66
  17. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +15 -9
  18. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +15 -6
  19. package/dist/es/layouts/history-list/composables.js +72 -2
  20. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +5 -1
  21. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  22. package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +3 -3
  23. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
  25. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  26. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
  27. package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +5 -5
  28. package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
  29. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +1 -1
  30. package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
  31. package/dist/es/services/codeBlock.js +25 -10
  32. package/dist/es/services/dataSource.js +24 -10
  33. package/dist/es/services/editor.js +98 -46
  34. package/dist/es/services/history.js +7 -2
  35. package/dist/es/services/keybinding.js +3 -3
  36. package/dist/es/style.css +60 -16
  37. package/dist/es/utils/content-menu.js +17 -9
  38. package/dist/style.css +60 -16
  39. package/dist/tmagic-editor.umd.cjs +795 -443
  40. package/package.json +7 -7
  41. package/src/Editor.vue +8 -0
  42. package/src/components/CompareForm.vue +50 -19
  43. package/src/editorProps.ts +7 -0
  44. package/src/fields/CodeLink.vue +2 -5
  45. package/src/hooks/use-code-block-edit.ts +4 -3
  46. package/src/hooks/use-data-source-edit.ts +2 -2
  47. package/src/hooks/use-stage.ts +4 -3
  48. package/src/index.ts +2 -0
  49. package/src/layouts/CodeEditor.vue +2 -5
  50. package/src/layouts/NavMenu.vue +1 -1
  51. package/src/layouts/history-list/Bucket.vue +58 -29
  52. package/src/layouts/history-list/BucketTab.vue +80 -0
  53. package/src/layouts/history-list/GroupRow.vue +110 -58
  54. package/src/layouts/history-list/HistoryDiffDialog.vue +53 -33
  55. package/src/layouts/history-list/HistoryListPanel.vue +165 -52
  56. package/src/layouts/history-list/InitialRow.vue +17 -6
  57. package/src/layouts/history-list/PageTab.vue +25 -7
  58. package/src/layouts/history-list/composables.ts +92 -1
  59. package/src/layouts/props-panel/PropsPanel.vue +5 -1
  60. package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
  61. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
  62. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
  63. package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
  64. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  65. package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
  66. package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
  67. package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
  68. package/src/layouts/workspace/viewer/Stage.vue +1 -1
  69. package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
  70. package/src/services/codeBlock.ts +33 -9
  71. package/src/services/dataSource.ts +30 -8
  72. package/src/services/editor.ts +111 -34
  73. package/src/services/history.ts +10 -0
  74. package/src/services/keybinding.ts +3 -3
  75. package/src/theme/history-list-panel.scss +67 -14
  76. package/src/theme/props-panel.scss +3 -3
  77. package/src/type.ts +146 -0
  78. package/src/utils/content-menu.ts +18 -9
  79. package/types/index.d.ts +387 -156
  80. package/dist/es/layouts/history-list/CodeBlockTab.js +0 -5
  81. package/dist/es/layouts/history-list/DataSourceTab.js +0 -5
  82. package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +0 -62
  83. package/src/layouts/history-list/CodeBlockTab.vue +0 -61
  84. package/src/layouts/history-list/DataSourceTab.vue +0 -61
@@ -1,12 +1,11 @@
1
1
  import { useServices } from "../../hooks/use-services.js";
2
2
  import Icon_default from "../../components/Icon.js";
3
- import { useHistoryList } from "./composables.js";
4
- import CodeBlockTab_default from "./CodeBlockTab.js";
5
- import DataSourceTab_default from "./DataSourceTab.js";
3
+ import { describeCodeBlockGroup, describeCodeBlockStep, describeDataSourceGroup, describeDataSourceStep, isCodeBlockStepRevertable, isDataSourceStepRevertable, useHistoryList } from "./composables.js";
4
+ import BucketTab_default from "./BucketTab.js";
6
5
  import HistoryDiffDialog_default from "./HistoryDiffDialog.js";
7
6
  import PageTab_default from "./PageTab.js";
8
7
  import { TMagicButton, TMagicPopover, TMagicTabs, TMagicTooltip, getDesignConfig } from "@tmagic/design";
9
- import { Fragment, createBlock, createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, inject, markRaw, normalizeProps, openBlock, ref, resolveDynamicComponent, unref, useTemplateRef, withCtx } from "vue";
8
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, inject, markRaw, mergeProps, normalizeProps, openBlock, ref, renderList, resolveDynamicComponent, shallowRef, toHandlers, unref, useTemplateRef, watch, withCtx } from "vue";
10
9
  import { Clock, Close } from "@element-plus/icons-vue";
11
10
  //#region packages/editor/src/layouts/history-list/HistoryListPanel.vue?vue&type=script&setup=true&lang.ts
12
11
  var _hoisted_1 = { class: "m-editor-history-list" };
@@ -31,7 +30,8 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
31
30
  * 此外每条 step 上提供"查看差异"入口(仅在前后值都存在的 update 步骤显示),
32
31
  * 点击后弹出 HistoryDiffDialog,使用 CompareForm 组件以表单形式展示新旧值差异。
33
32
  *
34
- * 各 tab 的内容拆分为独立的 SFCPageTab / DataSourceTab / CodeBlockTab),
33
+ * 各 tab 的内容拆分为独立的 SFC:页面用 PageTab,数据源 / 代码块复用通用的 BucketTab
34
+ * (通过 title / prefix / describe* / isStepDiffable 注入差异)。
35
35
  * 共享的描述生成与折叠状态在 composables.ts 中维护。
36
36
  */
37
37
  const ClockIcon = markRaw(Clock);
@@ -40,7 +40,23 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
40
40
  /** 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。 */
41
41
  const visible = ref(false);
42
42
  const tabPaneComponent = getDesignConfig("components")?.tabPane;
43
- const { editorService, dataSourceService, codeBlockService, historyService } = useServices();
43
+ /**
44
+ * 业务方自定义的扩展 tab,由 Editor 顶层通过 `historyListExtraTabs` 注入。
45
+ * 追加在内置「页面 / 数据源 / 代码块」三个 tab 之后,未提供时为空数组。
46
+ */
47
+ const extraTabs = inject("historyListExtraTabs", []);
48
+ /** label 支持字符串或函数,函数形式便于展示动态数量等内容。 */
49
+ const resolveTabLabel = (tab) => typeof tab.label === "function" ? tab.label() : tab.label;
50
+ const { editorService, dataSourceService, codeBlockService, historyService, propsService } = useServices();
51
+ /**
52
+ * 数据源 / 代码块功能可被业务方通过 `disabledDataSource` / `disabledCodeBlock` 禁用,
53
+ * 禁用后对应的历史记录 tab 不再展示。若当前激活的 tab 恰好被禁用,则回退到「页面」tab。
54
+ */
55
+ const disabledDataSource = computed(() => propsService.getDisabledDataSource());
56
+ const disabledCodeBlock = computed(() => propsService.getDisabledCodeBlock());
57
+ watch([disabledDataSource, disabledCodeBlock], ([dsDisabled, cbDisabled]) => {
58
+ if (activeTab.value === "data-source" && dsDisabled || activeTab.value === "code-block" && cbDisabled) activeTab.value = "page";
59
+ });
44
60
  /**
45
61
  * 通过 inject 拿到 Editor 顶层注入的 `extendFormState`,转交给 HistoryDiffDialog
46
62
  * 内部的 CompareForm,使差异对比表单的 filterFunction 能拿到完整的业务上下文。
@@ -48,6 +64,10 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
48
64
  */
49
65
  const extendFormState = inject("extendFormState", void 0);
50
66
  const { expanded, toggleGroup, pageGroups, dataSourceGroups, codeBlockGroups, pageGroupsDisplay, dataSourceGroupsByTarget, codeBlockGroupsByTarget } = useHistoryList();
67
+ /** 数据源 step 仅 update(前后 schema 都存在)时可查看差异。 */
68
+ const isDataSourceStepDiffable = (step) => Boolean(step.oldSchema && step.newSchema);
69
+ /** 代码块 step 仅 update(前后 content 都存在)时可查看差异。 */
70
+ const isCodeBlockStepDiffable = (step) => Boolean(step.oldContent && step.newContent);
51
71
  /** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
52
72
  const indexToCursor = (index) => index + 1;
53
73
  const onPageGoto = (index) => {
@@ -72,36 +92,24 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
72
92
  const onCodeBlockGotoInitial = (id) => {
73
93
  codeBlockService.goto(id, 0);
74
94
  };
75
- /**
76
- * 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
77
- * 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
78
- */
79
- const onPageRevert = (index) => {
80
- editorService.revertPageStep(index);
81
- };
82
- const onDataSourceRevert = (id, index) => {
83
- dataSourceService.revert(id, index);
84
- };
85
- const onCodeBlockRevert = (id, index) => {
86
- codeBlockService.revert(id, index);
87
- };
88
95
  const diffDialogRef = useTemplateRef("diffDialog");
89
96
  /**
90
- * 页面 step 差异:仅 update 单节点修改可对比,传入旧/新节点。
97
+ * 构造页面 step 的差异弹窗入参:仅 update 单节点修改可对比,传入旧/新节点。
91
98
  * 节点类型 `type` 优先取 newNode.type,再回退 oldNode.type。
92
99
  * `currentValue` 取自 editorService 中该节点当前实际值,用于支持「与当前对比」。
100
+ * 无可对比内容(如多节点 / add / remove)时返回 null。
93
101
  */
94
- const onPageDiff = (index) => {
102
+ const buildPageDiffPayload = (index) => {
95
103
  const groups = historyService.getPageHistoryGroups();
96
104
  for (const group of groups) {
97
105
  const entry = group.steps.find((s) => s.index === index);
98
106
  if (!entry) continue;
99
107
  const item = entry.step.updatedItems?.[0];
100
- if (!item?.oldNode || !item?.newNode) return;
108
+ if (!item?.oldNode || !item?.newNode) return null;
101
109
  const type = item.newNode.type || item.oldNode.type || "";
102
110
  const nodeId = item.newNode.id ?? item.oldNode.id;
103
111
  const currentNode = nodeId !== void 0 ? editorService.getNodeById(nodeId) : null;
104
- diffDialogRef.value?.open({
112
+ return {
105
113
  category: "node",
106
114
  type,
107
115
  lastValue: item.oldNode,
@@ -109,50 +117,87 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
109
117
  currentValue: currentNode || null,
110
118
  targetLabel: item.newNode.name || item.oldNode.name || type,
111
119
  id: nodeId
112
- });
113
- return;
120
+ };
114
121
  }
122
+ return null;
115
123
  };
116
- const onDataSourceDiff = (id, index) => {
117
- const groups = historyService.getDataSourceHistoryGroups();
124
+ /**
125
+ * 在指定分组列表中按 id / index 查找命中的 step,命中后交由 build 构造差异弹窗入参。
126
+ * 用于统一数据源、代码块两类历史的查找逻辑。
127
+ */
128
+ const findGroupStep = (groups, id, index, build) => {
118
129
  for (const group of groups) {
119
130
  if (group.id !== id) continue;
120
131
  const entry = group.steps.find((s) => s.index === index);
121
132
  if (!entry) continue;
122
- const { oldSchema, newSchema } = entry.step;
123
- if (!oldSchema || !newSchema) return;
124
- const currentSchema = dataSourceService.getDataSourceById(`${id}`);
125
- diffDialogRef.value?.open({
126
- category: "data-source",
127
- type: newSchema.type || oldSchema.type || "base",
128
- lastValue: oldSchema,
129
- value: newSchema,
130
- currentValue: currentSchema || null,
131
- targetLabel: newSchema.title || oldSchema.title || `${id}`,
132
- id
133
- });
134
- return;
133
+ return build(entry.step);
135
134
  }
135
+ return null;
136
+ };
137
+ const buildDataSourceDiffPayload = (id, index) => findGroupStep(historyService.getDataSourceHistoryGroups(), id, index, ({ oldSchema, newSchema }) => {
138
+ if (!oldSchema || !newSchema) return null;
139
+ const currentSchema = dataSourceService.getDataSourceById(`${id}`);
140
+ return {
141
+ category: "data-source",
142
+ type: newSchema.type || oldSchema.type || "base",
143
+ lastValue: oldSchema,
144
+ value: newSchema,
145
+ currentValue: currentSchema || null,
146
+ targetLabel: newSchema.title || oldSchema.title || `${id}`,
147
+ id
148
+ };
149
+ });
150
+ const buildCodeBlockDiffPayload = (id, index) => findGroupStep(historyService.getCodeBlockHistoryGroups(), id, index, ({ oldContent, newContent }) => {
151
+ if (!oldContent || !newContent) return null;
152
+ return {
153
+ category: "code-block",
154
+ lastValue: oldContent,
155
+ value: newContent,
156
+ currentValue: codeBlockService.getCodeContentById(id) || null,
157
+ targetLabel: newContent.name || oldContent.name || `${id}`,
158
+ id
159
+ };
160
+ });
161
+ const onPageDiff = (index) => {
162
+ const payload = buildPageDiffPayload(index);
163
+ if (payload) diffDialogRef.value?.open(payload);
164
+ };
165
+ const onDataSourceDiff = (id, index) => {
166
+ const payload = buildDataSourceDiffPayload(id, index);
167
+ if (payload) diffDialogRef.value?.open(payload);
136
168
  };
137
169
  const onCodeBlockDiff = (id, index) => {
138
- const groups = historyService.getCodeBlockHistoryGroups();
139
- for (const group of groups) {
140
- if (group.id !== id) continue;
141
- const entry = group.steps.find((s) => s.index === index);
142
- if (!entry) continue;
143
- const { oldContent, newContent } = entry.step;
144
- if (!oldContent || !newContent) return;
145
- const currentContent = codeBlockService.getCodeContentById(id);
146
- diffDialogRef.value?.open({
147
- category: "code-block",
148
- lastValue: oldContent,
149
- value: newContent,
150
- currentValue: currentContent || null,
151
- targetLabel: newContent.name || oldContent.name || `${id}`,
152
- id
153
- });
154
- return;
155
- }
170
+ const payload = buildCodeBlockDiffPayload(id, index);
171
+ if (payload) diffDialogRef.value?.open(payload);
172
+ };
173
+ const onConfirmRevert = shallowRef();
174
+ /**
175
+ * 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
176
+ * 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
177
+ *
178
+ * 交互:先弹出该步骤的差异弹窗供用户确认,点击「确定回滚」后再真正执行回滚;
179
+ * 对没有可对比内容的步骤(如 add / remove / 多节点更新)则直接回滚。
180
+ */
181
+ const onPageRevert = (index) => {
182
+ const payload = buildPageDiffPayload(index);
183
+ onConfirmRevert.value = () => editorService.revertPageStep(index);
184
+ if (payload) diffDialogRef.value?.open({ ...payload });
185
+ else onConfirmRevert.value();
186
+ };
187
+ const onDataSourceRevert = (id, index) => {
188
+ const payload = buildDataSourceDiffPayload(id, index);
189
+ onConfirmRevert.value = () => dataSourceService.revert(id, index);
190
+ if (payload) diffDialogRef.value?.open({ ...payload });
191
+ else onConfirmRevert.value();
192
+ };
193
+ const onCodeBlockRevert = (id, index) => {
194
+ const payload = buildCodeBlockDiffPayload(id, index);
195
+ onConfirmRevert.value = () => codeBlockService.revert(id, index);
196
+ if (payload) diffDialogRef.value?.open({ ...payload });
197
+ else onConfirmRevert.value();
198
+ };
199
+ const onDiffDialogClose = () => {
200
+ onConfirmRevert.value = void 0;
156
201
  };
157
202
  return (_ctx, _cache) => {
158
203
  return openBlock(), createElementBlock(Fragment, null, [createVNode(unref(TMagicPopover), {
@@ -217,13 +262,19 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
217
262
  ])]),
218
263
  _: 1
219
264
  }, 16)),
220
- (openBlock(), createBlock(resolveDynamicComponent(unref(tabPaneComponent)?.component || "el-tab-pane"), normalizeProps(guardReactiveProps(unref(tabPaneComponent)?.props({
265
+ !disabledDataSource.value ? (openBlock(), createBlock(resolveDynamicComponent(unref(tabPaneComponent)?.component || "el-tab-pane"), normalizeProps(mergeProps({ key: 0 }, unref(tabPaneComponent)?.props({
221
266
  name: "data-source",
222
267
  label: `数据源 (${unref(dataSourceGroups).length})`
223
268
  }) || {})), {
224
- default: withCtx(() => [createVNode(DataSourceTab_default, {
269
+ default: withCtx(() => [createVNode(BucketTab_default, {
270
+ title: "数据源",
271
+ prefix: "ds",
225
272
  buckets: unref(dataSourceGroupsByTarget),
226
273
  expanded: unref(expanded),
274
+ "describe-group": unref(describeDataSourceGroup),
275
+ "describe-step": unref(describeDataSourceStep),
276
+ "is-step-diffable": isDataSourceStepDiffable,
277
+ "is-step-revertable": unref(isDataSourceStepRevertable),
227
278
  onToggle: unref(toggleGroup),
228
279
  onGoto: onDataSourceGoto,
229
280
  onGotoInitial: onDataSourceGotoInitial,
@@ -232,17 +283,26 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
232
283
  }, null, 8, [
233
284
  "buckets",
234
285
  "expanded",
286
+ "describe-group",
287
+ "describe-step",
288
+ "is-step-revertable",
235
289
  "onToggle"
236
290
  ])]),
237
291
  _: 1
238
- }, 16)),
239
- (openBlock(), createBlock(resolveDynamicComponent(unref(tabPaneComponent)?.component || "el-tab-pane"), normalizeProps(guardReactiveProps(unref(tabPaneComponent)?.props({
292
+ }, 16)) : createCommentVNode("v-if", true),
293
+ !disabledCodeBlock.value ? (openBlock(), createBlock(resolveDynamicComponent(unref(tabPaneComponent)?.component || "el-tab-pane"), normalizeProps(mergeProps({ key: 1 }, unref(tabPaneComponent)?.props({
240
294
  name: "code-block",
241
295
  label: `代码块 (${unref(codeBlockGroups).length})`
242
296
  }) || {})), {
243
- default: withCtx(() => [createVNode(CodeBlockTab_default, {
297
+ default: withCtx(() => [createVNode(BucketTab_default, {
298
+ title: "代码块",
299
+ prefix: "cb",
244
300
  buckets: unref(codeBlockGroupsByTarget),
245
301
  expanded: unref(expanded),
302
+ "describe-group": unref(describeCodeBlockGroup),
303
+ "describe-step": unref(describeCodeBlockStep),
304
+ "is-step-diffable": isCodeBlockStepDiffable,
305
+ "is-step-revertable": unref(isCodeBlockStepRevertable),
246
306
  onToggle: unref(toggleGroup),
247
307
  onGoto: onCodeBlockGoto,
248
308
  onGotoInitial: onCodeBlockGotoInitial,
@@ -251,18 +311,32 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
251
311
  }, null, 8, [
252
312
  "buckets",
253
313
  "expanded",
314
+ "describe-group",
315
+ "describe-step",
316
+ "is-step-revertable",
254
317
  "onToggle"
255
318
  ])]),
256
319
  _: 1
257
- }, 16))
320
+ }, 16)) : createCommentVNode("v-if", true),
321
+ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(extraTabs), (tab) => {
322
+ return openBlock(), createBlock(resolveDynamicComponent(unref(tabPaneComponent)?.component || "el-tab-pane"), mergeProps({ key: tab.name }, { ref_for: true }, unref(tabPaneComponent)?.props({
323
+ name: tab.name,
324
+ label: resolveTabLabel(tab)
325
+ }) || {}), {
326
+ default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(tab.component), mergeProps({ ref_for: true }, tab.props || {}, toHandlers(tab.listeners || {})), null, 16))]),
327
+ _: 2
328
+ }, 1040);
329
+ }), 128))
258
330
  ]),
259
331
  _: 1
260
332
  }, 8, ["modelValue"])])]),
261
333
  _: 1
262
334
  }, 8, ["visible"]), createVNode(HistoryDiffDialog_default, {
263
335
  ref: "diffDialog",
264
- "extend-state": unref(extendFormState)
265
- }, null, 8, ["extend-state"])], 64);
336
+ "extend-state": unref(extendFormState),
337
+ "on-confirm": onConfirmRevert.value,
338
+ onClose: onDiffDialogClose
339
+ }, null, 8, ["extend-state", "on-confirm"])], 64);
266
340
  };
267
341
  }
268
342
  });
@@ -1,14 +1,16 @@
1
- import { createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock } from "vue";
1
+ import { createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, withModifiers } from "vue";
2
2
  //#region packages/editor/src/layouts/history-list/InitialRow.vue?vue&type=script&setup=true&lang.ts
3
3
  var _hoisted_1 = ["title"];
4
- var _hoisted_2 = {
5
- key: 0,
6
- class: "m-editor-history-list-item-current"
7
- };
8
4
  var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
9
5
  name: "MEditorHistoryListInitialRow",
10
6
  __name: "InitialRow",
11
- props: { isCurrent: { type: Boolean } },
7
+ props: {
8
+ isCurrent: { type: Boolean },
9
+ gotoEnabled: {
10
+ type: Boolean,
11
+ default: true
12
+ }
13
+ },
12
14
  emits: ["goto-initial"],
13
15
  setup(__props, { emit: __emit }) {
14
16
  /**
@@ -30,8 +32,7 @@ var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
30
32
  "is-current": __props.isCurrent,
31
33
  "is-clickable": !__props.isCurrent
32
34
  }]),
33
- title: __props.isCurrent ? "当前已回到未修改的初始状态" : "点击回到未修改的初始状态",
34
- onClick
35
+ title: __props.isCurrent ? "当前已回到未修改的初始状态" : "点击回到未修改的初始状态"
35
36
  }, [
36
37
  _cache[0] || (_cache[0] = createElementVNode("span", {
37
38
  class: "m-editor-history-list-item-index",
@@ -39,7 +40,12 @@ var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
39
40
  }, "#0", -1)),
40
41
  _cache[1] || (_cache[1] = createElementVNode("span", { class: "m-editor-history-list-item-op op-initial" }, "初始", -1)),
41
42
  _cache[2] || (_cache[2] = createElementVNode("span", { class: "m-editor-history-list-item-desc" }, "未修改的初始状态", -1)),
42
- __props.isCurrent ? (openBlock(), createElementBlock("span", _hoisted_2, "当前")) : createCommentVNode("v-if", true)
43
+ __props.gotoEnabled && !__props.isCurrent ? (openBlock(), createElementBlock("span", {
44
+ key: 0,
45
+ class: "m-editor-history-list-item-goto",
46
+ title: "回到该记录",
47
+ onClick: withModifiers(onClick, ["stop"])
48
+ }, "回到")) : createCommentVNode("v-if", true)
43
49
  ], 10, _hoisted_1);
44
50
  };
45
51
  }
@@ -1,4 +1,4 @@
1
- import { describePageGroup, describePageStep } from "./composables.js";
1
+ import { describePageGroup, describePageStep, formatHistoryFullTime, formatHistoryTime, groupSource, groupTimestamp, isPageStepRevertable } from "./composables.js";
2
2
  import GroupRow_default from "./GroupRow.js";
3
3
  import InitialRow_default from "./InitialRow.js";
4
4
  import { TMagicScrollbar } from "@tmagic/design";
@@ -49,14 +49,17 @@ var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
49
49
  "max-height": "360px"
50
50
  }, {
51
51
  default: withCtx(() => [createElementVNode("ul", _hoisted_2, [
52
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.list, (group, gIdx) => {
52
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.list, (group) => {
53
53
  return openBlock(), createBlock(GroupRow_default, {
54
- key: `pg-${gIdx}`,
55
- "group-key": `pg-${gIdx}`,
54
+ key: `pg-${group.steps[0]?.index}`,
55
+ "group-key": `pg-${group.steps[0]?.index}`,
56
56
  applied: group.applied,
57
57
  merged: group.steps.length > 1,
58
58
  "op-type": group.opType,
59
59
  desc: unref(describePageGroup)(group),
60
+ source: unref(groupSource)(group),
61
+ time: unref(formatHistoryTime)(unref(groupTimestamp)(group)),
62
+ "time-title": unref(formatHistoryFullTime)(unref(groupTimestamp)(group)),
60
63
  "step-count": group.steps.length,
61
64
  "sub-steps": group.steps.map((s) => ({
62
65
  index: s.index,
@@ -64,10 +67,13 @@ var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
64
67
  isCurrent: s.isCurrent,
65
68
  desc: unref(describePageStep)(s.step),
66
69
  diffable: isPageStepDiffable(s.step),
67
- revertable: s.applied
70
+ revertable: s.applied && unref(isPageStepRevertable)(s.step),
71
+ source: s.step.source,
72
+ time: unref(formatHistoryTime)(s.step.timestamp),
73
+ timeTitle: unref(formatHistoryFullTime)(s.step.timestamp)
68
74
  })),
69
75
  "is-current": group.isCurrent,
70
- expanded: !!__props.expanded[`pg-${gIdx}`],
76
+ expanded: !!__props.expanded[`pg-${group.steps[0]?.index}`],
71
77
  onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
72
78
  onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", index)),
73
79
  onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", index)),
@@ -78,6 +84,9 @@ var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
78
84
  "merged",
79
85
  "op-type",
80
86
  "desc",
87
+ "source",
88
+ "time",
89
+ "time-title",
81
90
  "step-count",
82
91
  "sub-steps",
83
92
  "is-current",
@@ -1,4 +1,5 @@
1
1
  import { useServices } from "../../hooks/use-services.js";
2
+ import { datetimeFormatter } from "@tmagic/form";
2
3
  import { computed, reactive } from "vue";
3
4
  //#region packages/editor/src/layouts/history-list/composables.ts
4
5
  /**
@@ -11,7 +12,10 @@ import { computed, reactive } from "vue";
11
12
  */
12
13
  var useHistoryList = () => {
13
14
  const { historyService } = useServices();
14
- /** 折叠状态:key 形如 `pg-${groupIdx}` / `ds-${id}-${groupIdx}` / `cb-${id}-${groupIdx}`。 */
15
+ /**
16
+ * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
17
+ * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
18
+ */
15
19
  const expanded = reactive({});
16
20
  const toggleGroup = (key) => {
17
21
  expanded[key] = !expanded[key];
@@ -48,6 +52,20 @@ var useHistoryList = () => {
48
52
  codeBlockGroupsByTarget: computed(() => groupByTarget(codeBlockGroups.value))
49
53
  };
50
54
  };
55
+ /**
56
+ * 历史面板的时间展示:
57
+ * - 当天的记录只显示 `HH:mm:ss`;
58
+ * - 跨天的记录显示 `MM-DD HH:mm:ss`。
59
+ * 无时间戳(旧数据 / 未写入)时返回空串,UI 据此不渲染时间。
60
+ */
61
+ var formatHistoryTime = (timestamp) => {
62
+ if (!timestamp) return "";
63
+ return `${datetimeFormatter(new Date(timestamp), "", "YYYY-MM-DD") === datetimeFormatter(/* @__PURE__ */ new Date(), "", "YYYY-MM-DD") ? datetimeFormatter(new Date(timestamp), "", "HH:mm:ss") : datetimeFormatter(new Date(timestamp), "", "MM-DD HH:mm:ss")}`;
64
+ };
65
+ /** 完整时间(含年份与秒),用于 title 悬浮提示。无时间戳时返回空串。 */
66
+ var formatHistoryFullTime = (timestamp) => timestamp ? `${datetimeFormatter(new Date(timestamp), "", "YYYY-MM-DD HH:mm:ss")}` : "";
67
+ /** 取一组历史步骤里最后一步(最近一次)的时间戳,用于组头部展示。 */
68
+ var groupTimestamp = (group) => group.steps[group.steps.length - 1]?.step.timestamp;
51
69
  var opLabel = (op) => {
52
70
  switch (op) {
53
71
  case "add": return "新增";
@@ -55,6 +73,28 @@ var opLabel = (op) => {
55
73
  default: return "修改";
56
74
  }
57
75
  };
76
+ /** 内置操作途径的中文文案;自定义来源直接回显原值,未知 / 缺省返回空串(UI 据此不渲染)。 */
77
+ var HISTORY_SOURCE_LABELS = {
78
+ stage: "画布",
79
+ tree: "树面板",
80
+ "component-panel": "组件面板",
81
+ props: "配置面板",
82
+ code: "源码",
83
+ "stage-contextmenu": "画布菜单",
84
+ "tree-contextmenu": "树菜单",
85
+ toolbar: "工具栏",
86
+ shortcut: "快捷键",
87
+ rollback: "回滚",
88
+ api: "接口",
89
+ ai: "AI",
90
+ unknown: "未知"
91
+ };
92
+ /** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
93
+ var sourceLabel = (source = "unknown") => {
94
+ return HISTORY_SOURCE_LABELS[source] ?? `${source}`;
95
+ };
96
+ /** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
97
+ var groupSource = (group) => group.steps[group.steps.length - 1]?.step.source;
58
98
  var nameOf = (node) => node?.name || node?.type || `${node?.id ?? ""}`;
59
99
  /**
60
100
  * 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
@@ -150,5 +190,35 @@ var describeCodeBlockGroup = (group) => {
150
190
  const target = labelWithId(group.steps[group.steps.length - 1].step.newContent?.name || group.steps[0].step.oldContent?.name, group.id);
151
191
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
152
192
  };
193
+ /**
194
+ * 页面 step 是否支持「回滚」(类 git revert):
195
+ * - 新增 / 删除:不依赖 changeRecords,反向应用即删除 / 加回,始终可回滚;
196
+ * - 更新:必须每个被更新节点都带有 changeRecords,才支持按 propPath 局部反向 patch。
197
+ * 缺失 changeRecords 的更新只能整节点替换,会冲掉该节点后续的无关变更,因此不支持回滚。
198
+ */
199
+ var isPageStepRevertable = (step) => {
200
+ if (step.opType !== "update") return true;
201
+ const items = step.updatedItems ?? [];
202
+ if (!items.length) return false;
203
+ return items.every((item) => Boolean(item.changeRecords?.length));
204
+ };
205
+ /**
206
+ * 数据源 step 是否支持「回滚」:
207
+ * - 新增(oldSchema=null)/ 删除(newSchema=null):不依赖 changeRecords,始终可回滚;
208
+ * - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
209
+ */
210
+ var isDataSourceStepRevertable = (step) => {
211
+ if (step.oldSchema === null || step.newSchema === null) return true;
212
+ return Boolean(step.changeRecords?.length);
213
+ };
214
+ /**
215
+ * 代码块 step 是否支持「回滚」:
216
+ * - 新增(oldContent=null)/ 删除(newContent=null):不依赖 changeRecords,始终可回滚;
217
+ * - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
218
+ */
219
+ var isCodeBlockStepRevertable = (step) => {
220
+ if (step.oldContent === null || step.newContent === null) return true;
221
+ return Boolean(step.changeRecords?.length);
222
+ };
153
223
  //#endregion
154
- export { describeCodeBlockGroup, describeCodeBlockStep, describeDataSourceGroup, describeDataSourceStep, describePageGroup, describePageStep, opLabel, useHistoryList };
224
+ export { describeCodeBlockGroup, describeCodeBlockStep, describeDataSourceGroup, describeDataSourceStep, describePageGroup, describePageStep, formatHistoryFullTime, formatHistoryTime, groupSource, groupTimestamp, isCodeBlockStepRevertable, isDataSourceStepRevertable, isPageStepRevertable, opLabel, sourceLabel, useHistoryList };
@@ -69,7 +69,11 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
69
69
  if (record.propPath?.startsWith("style") && record.value === "") setValueByKeyPath(record.propPath, record.value, newValue);
70
70
  });
71
71
  }
72
- editorService.update(newValue, { changeRecords: eventData?.changeRecords });
72
+ const historySource = eventData ? "props" : "code";
73
+ editorService.update(newValue, {
74
+ changeRecords: eventData?.changeRecords,
75
+ historySource
76
+ });
73
77
  } catch (e) {
74
78
  emit("submit-error", e);
75
79
  }
@@ -37,7 +37,7 @@ var ComponentListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE_
37
37
  name: text,
38
38
  type,
39
39
  ...data
40
- });
40
+ }, void 0, { historySource: "component-panel" });
41
41
  };
42
42
  const dragstartHandler = ({ text, type, data = {} }, e) => {
43
43
  e.dataTransfer?.setData("text/json", serialize({
@@ -78,7 +78,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
78
78
  const editCode = (id) => {
79
79
  emit("edit", id);
80
80
  };
81
- const deleteCode = async (id) => {
81
+ const deleteCode = async (id, { historySource } = {}) => {
82
82
  const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
83
83
  const existBinds = Boolean(currentCode?.items?.length);
84
84
  const undeleteableList = codeBlockService.getUndeletableList() || [];
@@ -88,7 +88,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
88
88
  cancelButtonText: "取消",
89
89
  type: "warning"
90
90
  });
91
- emit("remove", id);
91
+ emit("remove", id, { historySource });
92
92
  } else if (typeof props.customError === "function") props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
93
93
  else if (existBinds) tMagicMessage.error("代码块存在绑定关系,不可删除");
94
94
  else tMagicMessage.error("代码块不可删除");
@@ -138,7 +138,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
138
138
  default: withCtx(() => [createVNode(Icon_default, {
139
139
  icon: unref(Close),
140
140
  class: "edit-icon",
141
- onClick: withModifiers(($event) => deleteCode(`${data.key}`), ["stop"])
141
+ onClick: withModifiers(($event) => deleteCode(`${data.key}`, { historySource: "tree" }), ["stop"])
142
142
  }, null, 8, ["icon", "onClick"])]),
143
143
  _: 2
144
144
  }, 1024)) : createCommentVNode("v-if", true),
@@ -38,7 +38,7 @@ var CodeBlockListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE_
38
38
  if (codeBlockListRef.value) for (const [, status] of codeBlockListRef.value.nodeStatusMap.entries()) status.selected = false;
39
39
  };
40
40
  const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu((id) => {
41
- codeBlockListRef.value?.deleteCode(id);
41
+ codeBlockListRef.value?.deleteCode(id, { historySource: "tree-contextmenu" });
42
42
  });
43
43
  const menuData = computed(() => props.customContentMenu(contentMenuData, "code-block"));
44
44
  return (_ctx, _cache) => {
@@ -26,7 +26,7 @@ var useContentMenu = (deleteCode) => {
26
26
  const codeBlock = codeBlockService.getCodeContentById(selectId);
27
27
  if (!codeBlock) return;
28
28
  const newCodeId = await codeBlockService.getUniqueId();
29
- codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock));
29
+ codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock), { historySource: "tree-contextmenu" });
30
30
  }
31
31
  },
32
32
  {
@@ -55,7 +55,7 @@ var DataSourceListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE
55
55
  cancelButtonText: "取消",
56
56
  type: "warning"
57
57
  });
58
- dataSourceService.remove(id);
58
+ dataSourceService.remove(id, { historySource: "tree-contextmenu" });
59
59
  };
60
60
  const dataSourceListRef = useTemplateRef("dataSourceList");
61
61
  const filterTextChangeHandler = (val) => {
@@ -25,7 +25,7 @@ var useContentMenu = () => {
25
25
  if (!selectId) return;
26
26
  const ds = dataSourceService.getDataSourceById(selectId);
27
27
  if (!ds) return;
28
- dataSourceService.add(cloneDeep(ds));
28
+ dataSourceService.add(cloneDeep(ds), { historySource: "tree-contextmenu" });
29
29
  }
30
30
  },
31
31
  {
@@ -32,7 +32,7 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
32
32
  name: component.text,
33
33
  type: component.type,
34
34
  ...component.data || {}
35
- });
35
+ }, void 0, { historySource: "tree-contextmenu" });
36
36
  }
37
37
  }));
38
38
  const getSubMenuData = computed(() => {
@@ -41,7 +41,7 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
41
41
  type: "button",
42
42
  icon: Files,
43
43
  handler: () => {
44
- editorService.add({ type: "tab-pane" });
44
+ editorService.add({ type: "tab-pane" }, void 0, { historySource: "tree-contextmenu" });
45
45
  }
46
46
  }];
47
47
  if (node.value?.items) return componentList.value.reduce((subMenuData, group, index) => subMenuData.concat(createMenuItems(group), index < componentList.value.length - 1 ? [{
@@ -68,9 +68,9 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
68
68
  items: getSubMenuData.value
69
69
  },
70
70
  useCopyMenu(),
71
- usePasteMenu(),
72
- useDeleteMenu(),
73
- useMoveToMenu(services),
71
+ usePasteMenu("tree-contextmenu"),
72
+ useDeleteMenu("tree-contextmenu"),
73
+ useMoveToMenu(services, "tree-contextmenu"),
74
74
  ...props.layerContentMenu
75
75
  ], "layer"));
76
76
  const show = (e) => {
@@ -13,7 +13,7 @@ var LayerNodeTool_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
13
13
  editorService.update({
14
14
  id: props.data.id,
15
15
  visible
16
- });
16
+ }, { historySource: "tree" });
17
17
  };
18
18
  return (_ctx, _cache) => {
19
19
  return __props.data.type !== "page" ? (openBlock(), createBlock(unref(TMagicButton), {