@tmagic/editor 1.8.0-beta.3 → 1.8.0-beta.5

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 (92) hide show
  1. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +15 -1
  2. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
  3. package/dist/es/hooks/use-code-block-edit.js +6 -3
  4. package/dist/es/hooks/use-data-source-edit.js +5 -2
  5. package/dist/es/hooks/use-stage.js +6 -3
  6. package/dist/es/index.js +6 -3
  7. package/dist/es/initService.js +1 -1
  8. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -51
  11. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -36
  12. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +152 -93
  13. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +36 -10
  14. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +334 -208
  15. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
  16. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -57
  17. package/dist/es/layouts/history-list/composables.js +118 -25
  18. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
  19. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +5 -1
  20. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -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 +105 -42
  32. package/dist/es/services/dataSource.js +85 -35
  33. package/dist/es/services/editor.js +317 -122
  34. package/dist/es/services/history.js +270 -204
  35. package/dist/es/services/keybinding.js +3 -3
  36. package/dist/es/services/ui.js +3 -0
  37. package/dist/es/style.css +65 -10
  38. package/dist/es/utils/content-menu.js +17 -9
  39. package/dist/es/utils/editor.js +35 -1
  40. package/dist/es/utils/history.js +223 -0
  41. package/dist/es/utils/indexed-db.js +86 -0
  42. package/dist/es/utils/undo-redo.js +60 -1
  43. package/dist/style.css +65 -10
  44. package/dist/tmagic-editor.umd.cjs +2047 -954
  45. package/package.json +7 -7
  46. package/src/components/CompareForm.vue +21 -1
  47. package/src/components/ToolButton.vue +2 -2
  48. package/src/hooks/use-code-block-edit.ts +4 -3
  49. package/src/hooks/use-data-source-edit.ts +2 -2
  50. package/src/hooks/use-stage.ts +3 -3
  51. package/src/index.ts +1 -0
  52. package/src/initService.ts +1 -1
  53. package/src/layouts/Framework.vue +1 -1
  54. package/src/layouts/NavMenu.vue +1 -1
  55. package/src/layouts/history-list/Bucket.vue +34 -67
  56. package/src/layouts/history-list/BucketTab.vue +46 -51
  57. package/src/layouts/history-list/GroupRow.vue +150 -98
  58. package/src/layouts/history-list/HistoryDiffDialog.vue +96 -69
  59. package/src/layouts/history-list/HistoryListPanel.vue +298 -111
  60. package/src/layouts/history-list/InitialRow.vue +25 -9
  61. package/src/layouts/history-list/PageTab.vue +58 -48
  62. package/src/layouts/history-list/composables.ts +210 -29
  63. package/src/layouts/page-bar/PageBar.vue +4 -2
  64. package/src/layouts/props-panel/PropsPanel.vue +5 -1
  65. package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
  66. package/src/layouts/sidebar/Sidebar.vue +6 -1
  67. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
  68. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
  69. package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
  70. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  71. package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
  72. package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
  73. package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
  74. package/src/layouts/workspace/viewer/Stage.vue +1 -1
  75. package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
  76. package/src/services/codeBlock.ts +129 -35
  77. package/src/services/dataSource.ts +105 -34
  78. package/src/services/editor.ts +470 -157
  79. package/src/services/history.ts +312 -203
  80. package/src/services/keybinding.ts +3 -3
  81. package/src/services/ui.ts +7 -0
  82. package/src/theme/history-list-panel.scss +87 -6
  83. package/src/theme/page-bar.scss +0 -4
  84. package/src/theme/props-panel.scss +3 -3
  85. package/src/type.ts +221 -61
  86. package/src/utils/content-menu.ts +18 -9
  87. package/src/utils/editor.ts +41 -1
  88. package/src/utils/history.ts +298 -0
  89. package/src/utils/index.ts +2 -0
  90. package/src/utils/indexed-db.ts +122 -0
  91. package/src/utils/undo-redo.ts +88 -0
  92. package/types/index.d.ts +871 -308
@@ -4331,12 +4331,15 @@
4331
4331
  await (0, vue.nextTick)();
4332
4332
  codeBlockEditorRef.value?.show();
4333
4333
  };
4334
- const deleteCode = async (key) => {
4335
- codeBlockService.deleteCodeDslByIds([key]);
4334
+ const deleteCode = async (key, { historySource } = {}) => {
4335
+ codeBlockService.deleteCodeDslByIds([key], { historySource });
4336
4336
  };
4337
4337
  const submitCodeBlockHandler = async (values, eventData) => {
4338
4338
  if (!codeId.value) return;
4339
- await codeBlockService.setCodeDslById(codeId.value, values, { changeRecords: eventData?.changeRecords });
4339
+ await codeBlockService.setCodeDslById(codeId.value, values, {
4340
+ changeRecords: eventData?.changeRecords,
4341
+ historySource: "props"
4342
+ });
4340
4343
  codeBlockEditorRef.value?.hide();
4341
4344
  };
4342
4345
  return {
@@ -4914,8 +4917,41 @@
4914
4917
  };
4915
4918
  var props_default = new Props();
4916
4919
  //#endregion
4920
+ //#region packages/editor/src/utils/config.ts
4921
+ var $TMAGIC_EDITOR = {};
4922
+ var setEditorConfig = (option) => {
4923
+ $TMAGIC_EDITOR = option;
4924
+ };
4925
+ var getEditorConfig = (key) => $TMAGIC_EDITOR[key];
4926
+ //#endregion
4917
4927
  //#region packages/editor/src/utils/undo-redo.ts
4918
- var UndoRedo = class {
4928
+ var UndoRedo = class UndoRedo {
4929
+ /**
4930
+ * 由 {@link UndoRedo.serialize} 产出的快照重建一个 UndoRedo 实例。
4931
+ * 游标会被夹紧到 [0, length] 区间,避免脏数据导致越界。
4932
+ *
4933
+ * @param options.isSavedStep 可选谓词:若提供,则把游标定位到「最近一条满足该谓词的记录」之后
4934
+ * (即恢复到最近一个已保存点);找不到匹配记录时退回快照中的原游标。
4935
+ */
4936
+ static fromSerialized(data, options = {}) {
4937
+ const undoRedo = new UndoRedo(data.listMaxSize);
4938
+ const list = Array.isArray(data.elementList) ? data.elementList.map((item) => cloneDeep$1(item)) : [];
4939
+ let cursor = Number.isFinite(data.listCursor) ? data.listCursor : list.length;
4940
+ const overflow = list.length - undoRedo.listMaxSize;
4941
+ if (overflow > 0) {
4942
+ list.splice(0, overflow);
4943
+ cursor -= overflow;
4944
+ }
4945
+ if (options.isSavedStep) {
4946
+ for (let i = list.length - 1; i >= 0; i--) if (options.isSavedStep(list[i])) {
4947
+ cursor = i + 1;
4948
+ break;
4949
+ }
4950
+ }
4951
+ undoRedo.elementList = list;
4952
+ undoRedo.listCursor = Math.max(0, Math.min(cursor, list.length));
4953
+ return undoRedo;
4954
+ }
4919
4955
  elementList;
4920
4956
  listCursor;
4921
4957
  listMaxSize;
@@ -4925,6 +4961,17 @@
4925
4961
  this.listCursor = 0;
4926
4962
  this.listMaxSize = listMaxSize > minListMaxSize ? listMaxSize : minListMaxSize;
4927
4963
  }
4964
+ /**
4965
+ * 导出当前栈的可序列化快照(深克隆,避免外部改动污染内部状态)。
4966
+ * 配合 {@link UndoRedo.fromSerialized} 可在持久化后完整还原撤销/重做栈。
4967
+ */
4968
+ serialize() {
4969
+ return {
4970
+ elementList: this.elementList.map((item) => cloneDeep$1(item)),
4971
+ listCursor: this.listCursor,
4972
+ listMaxSize: this.listMaxSize
4973
+ };
4974
+ }
4928
4975
  pushElement(element) {
4929
4976
  this.elementList.splice(this.listCursor, this.elementList.length - this.listCursor, cloneDeep$1(element));
4930
4977
  this.listCursor += 1;
@@ -4957,6 +5004,28 @@
4957
5004
  return cloneDeep$1(this.elementList[this.listCursor - 1]);
4958
5005
  }
4959
5006
  /**
5007
+ * 用 `element` 替换当前游标所在元素(cursor - 1),并丢弃其后的重做尾部(与 {@link pushElement} 的丢尾一致),
5008
+ * 游标位置保持不变(元素数量不增)。cursor 为 0(无已应用元素)时不做任何操作并返回 false。
5009
+ * 用于「连续同类记录合并」等就地替换最新一条的场景。
5010
+ */
5011
+ replaceCurrentElement(element) {
5012
+ if (this.listCursor < 1) return false;
5013
+ this.elementList.splice(this.listCursor - 1, this.elementList.length - (this.listCursor - 1), cloneDeep$1(element));
5014
+ return true;
5015
+ }
5016
+ /**
5017
+ * 对当前游标所在元素(cursor - 1)做就地更新;cursor 为 0(全部已撤销)时不做任何操作。
5018
+ * 用于给「当前步骤」打标记(如标记为已保存)等元数据写入场景。
5019
+ */
5020
+ updateCurrentElement(updater) {
5021
+ if (this.listCursor < 1) return;
5022
+ updater(this.elementList[this.listCursor - 1]);
5023
+ }
5024
+ /** 对栈内全部元素做就地更新。用于批量清理元数据(如清空所有元素的已保存标记)。 */
5025
+ updateElements(updater) {
5026
+ this.elementList.forEach(updater);
5027
+ }
5028
+ /**
4960
5029
  * 返回栈内全部元素的浅克隆数组(按时间顺序,索引 0 为最早一步)。
4961
5030
  * 仅用于历史面板等只读展示场景,不应直接修改返回值。
4962
5031
  */
@@ -4978,172 +5047,317 @@
4978
5047
  }
4979
5048
  };
4980
5049
  //#endregion
4981
- //#region packages/editor/src/services/history.ts
4982
- var History = class History extends BaseService {
4983
- /**
4984
- * 把单个代码块栈拆成若干 group:
4985
- * - 把"新增/删除"独立成组(语义上属于一次性事件,不应与 update 合并);
4986
- * - 连续 'update' 合并到同一组,组内 steps 顺序就是发生顺序。
4987
- */
4988
- static mergeCodeBlockSteps(codeBlockId, list, cursor) {
4989
- const groups = [];
4990
- let current = null;
4991
- const currentIndex = cursor - 1;
4992
- list.forEach((step, index) => {
4993
- const opType = History.detectOpType(step.oldContent, step.newContent);
4994
- const applied = index < cursor;
4995
- const isCurrent = index === currentIndex;
4996
- if (opType === "update" && current?.opType === "update") {
4997
- current.steps.push({
4998
- step,
4999
- index,
5000
- applied,
5001
- isCurrent
5002
- });
5003
- current.applied = applied;
5004
- if (isCurrent) current.isCurrent = true;
5005
- } else {
5006
- current = {
5007
- kind: "code-block",
5008
- id: codeBlockId,
5009
- opType,
5010
- steps: [{
5011
- step,
5012
- index,
5013
- applied,
5014
- isCurrent
5015
- }],
5016
- applied,
5017
- isCurrent
5018
- };
5019
- groups.push(current);
5020
- }
5021
- });
5022
- return groups;
5023
- }
5024
- static mergeDataSourceSteps(dataSourceId, list, cursor) {
5025
- const groups = [];
5026
- let current = null;
5027
- const currentIndex = cursor - 1;
5028
- list.forEach((step, index) => {
5029
- const opType = History.detectOpType(step.oldSchema, step.newSchema);
5030
- const applied = index < cursor;
5031
- const isCurrent = index === currentIndex;
5032
- if (opType === "update" && current?.opType === "update") {
5033
- current.steps.push({
5050
+ //#region packages/editor/src/utils/history.ts
5051
+ /**
5052
+ * 「回滚」生成的新 step 简短描述。代码块 / 数据源共用。
5053
+ * 二者逻辑一致,仅展示名取值字段不同(代码块取 `name`,数据源取 `title`),
5054
+ * 因此通过 `getLabel` 注入取值方式。
5055
+ *
5056
+ * @param id 关联的代码块 / 数据源 id
5057
+ * @param diff 单条变更 diff(缺省视为空)
5058
+ * @param getLabel 从快照取展示名
5059
+ */
5060
+ var describeRevertStep = (id, { oldSchema, newSchema, changeRecords } = {}, getLabel) => {
5061
+ const labelOf = (schema) => getLabel(schema) || schema.id;
5062
+ if (!oldSchema && newSchema) return `撤回新增 ${labelOf(newSchema) || id}`;
5063
+ if (oldSchema && !newSchema) return `还原已删除的 ${labelOf(oldSchema) || id}`;
5064
+ const label = newSchema && getLabel(newSchema) || oldSchema && getLabel(oldSchema) || `${id}`;
5065
+ const propPath = changeRecords?.[0]?.propPath;
5066
+ return propPath ? `还原 ${label} · ${propPath}` : `还原 ${label}`;
5067
+ };
5068
+ /**
5069
+ * 根据 old/new 是否为 null 推断 opType(与 push 时的约定一致)。
5070
+ */
5071
+ var detectStackOpType = (oldVal, newVal) => {
5072
+ if (oldVal === null && newVal !== null) return "add";
5073
+ if (oldVal !== null && newVal === null) return "remove";
5074
+ return "update";
5075
+ };
5076
+ /**
5077
+ * 构造一条代码块 / 数据源「按 id 分栈」的历史记录:两者除 payload 字段命名外完全一致。
5078
+ *
5079
+ * - `add`:oldValue = null;`remove`:newValue = null;`update`:两者都有,可带 changeRecords 做局部更新。
5080
+ * - 内容会做 cloneDeep 防止后续被外部引用篡改;opType 依据 old/new 是否为 null 推断。
5081
+ * - 仅负责构造 step 并返回,入栈与事件 emit 由各公共方法(pushCodeBlock / pushDataSource)自行处理。
5082
+ * - 不直接驱动业务 service,调用方负责实际写回。
5083
+ */
5084
+ var createStackStep = (id, payload) => {
5085
+ if (!id) return null;
5086
+ const oldSchema = payload.oldValue ? cloneDeep$1(payload.oldValue) : null;
5087
+ const newSchema = payload.newValue ? cloneDeep$1(payload.newValue) : null;
5088
+ const changeRecords = payload.changeRecords?.length ? cloneDeep$1(payload.changeRecords) : void 0;
5089
+ const opType = detectStackOpType(payload.oldValue, payload.newValue);
5090
+ return {
5091
+ uuid: (0, _tmagic_utils.guid)(),
5092
+ id,
5093
+ opType,
5094
+ diff: [{
5095
+ ...newSchema !== null ? { newSchema } : {},
5096
+ ...oldSchema !== null ? { oldSchema } : {},
5097
+ ...opType === "update" && changeRecords ? { changeRecords } : {}
5098
+ }],
5099
+ historyDescription: payload.historyDescription,
5100
+ source: payload.source,
5101
+ timestamp: Date.now()
5102
+ };
5103
+ };
5104
+ var markStackSaved = (undoRedo) => {
5105
+ if (!undoRedo) return;
5106
+ undoRedo.updateElements((element) => {
5107
+ element.saved = false;
5108
+ });
5109
+ undoRedo.updateCurrentElement((element) => {
5110
+ element.saved = true;
5111
+ });
5112
+ };
5113
+ /**
5114
+ * 把单个「按 id 分栈」的历史栈(代码块 / 数据源)拆成若干 group:
5115
+ * - 把"新增/删除"独立成组(语义上属于一次性事件,不应与 update 合并);
5116
+ * - 连续 'update' 合并到同一组,组内 steps 顺序就是发生顺序。
5117
+ *
5118
+ * 代码块与数据源除 `kind` 外结构完全一致,统一由本方法处理;`kind` 决定返回的具体分组类型。
5119
+ */
5120
+ var mergeStackSteps = (kind, id, list, cursor) => {
5121
+ const groups = [];
5122
+ let current = null;
5123
+ const currentIndex = cursor - 1;
5124
+ list.forEach((step, index) => {
5125
+ const { opType } = step;
5126
+ const applied = index < cursor;
5127
+ const isCurrent = index === currentIndex;
5128
+ if (opType === "update" && current?.opType === "update") {
5129
+ current.steps.push({
5130
+ step,
5131
+ index,
5132
+ applied,
5133
+ isCurrent
5134
+ });
5135
+ current.applied = applied;
5136
+ if (isCurrent) current.isCurrent = true;
5137
+ } else {
5138
+ current = {
5139
+ kind,
5140
+ id,
5141
+ opType,
5142
+ steps: [{
5034
5143
  step,
5035
5144
  index,
5036
5145
  applied,
5037
5146
  isCurrent
5038
- });
5039
- current.applied = applied;
5040
- if (isCurrent) current.isCurrent = true;
5041
- } else {
5042
- current = {
5043
- kind: "data-source",
5044
- id: dataSourceId,
5045
- opType,
5046
- steps: [{
5047
- step,
5048
- index,
5049
- applied,
5050
- isCurrent
5051
- }],
5052
- applied,
5053
- isCurrent
5054
- };
5055
- groups.push(current);
5056
- }
5057
- });
5058
- return groups;
5059
- }
5060
- /**
5061
- * 根据 old/new 是否为 null 推断 opType(与 push 时的约定一致)。
5062
- */
5063
- static detectOpType(oldVal, newVal) {
5064
- if (oldVal === null && newVal !== null) return "add";
5065
- if (oldVal !== null && newVal === null) return "remove";
5066
- return "update";
5067
- }
5068
- /**
5069
- * 把页面栈拆成若干 group:
5070
- * - 单节点的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group;
5071
- * - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单节点修改组);
5072
- * - 多节点 'update'(如批量改属性)也独立成组(无明确单一目标,避免误合并)。
5073
- */
5074
- static mergePageSteps(pageId, list, cursor) {
5075
- const groups = [];
5076
- let current = null;
5077
- const currentIndex = cursor - 1;
5078
- list.forEach((step, index) => {
5079
- const applied = index < cursor;
5080
- const isCurrent = index === currentIndex;
5081
- const targetId = History.detectPageTargetId(step);
5082
- const targetName = History.detectPageTargetName(step);
5083
- const entry = {
5084
- step,
5085
- index,
5147
+ }],
5086
5148
  applied,
5087
5149
  isCurrent
5088
5150
  };
5089
- const mergeable = step.opType === "update" && targetId !== void 0;
5090
- if (mergeable && current?.opType === "update" && current.targetId === targetId) {
5091
- current.steps.push(entry);
5092
- current.applied = applied;
5093
- if (isCurrent) current.isCurrent = true;
5094
- if (targetName) current.targetName = targetName;
5095
- } else {
5096
- current = {
5097
- kind: "page",
5098
- pageId,
5099
- opType: step.opType,
5100
- targetId: mergeable ? targetId : void 0,
5101
- targetName,
5102
- steps: [entry],
5103
- applied,
5104
- isCurrent
5105
- };
5106
- groups.push(current);
5107
- }
5108
- });
5109
- return groups;
5151
+ groups.push(current);
5152
+ }
5153
+ });
5154
+ return groups;
5155
+ };
5156
+ /**
5157
+ * 把页面栈拆成若干 group:
5158
+ * - 单节点的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group;
5159
+ * - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单节点修改组);
5160
+ * - 多节点 'update'(如批量改属性)也独立成组(无明确单一目标,避免误合并)。
5161
+ */
5162
+ var mergePageSteps = (pageId, list, cursor) => {
5163
+ const groups = [];
5164
+ let current = null;
5165
+ const currentIndex = cursor - 1;
5166
+ list.forEach((step, index) => {
5167
+ const applied = index < cursor;
5168
+ const isCurrent = index === currentIndex;
5169
+ const targetId = detectPageTargetId(step);
5170
+ const targetName = detectPageTargetName(step);
5171
+ const entry = {
5172
+ step,
5173
+ index,
5174
+ applied,
5175
+ isCurrent
5176
+ };
5177
+ const mergeable = step.opType === "update" && targetId !== void 0;
5178
+ if (mergeable && current?.opType === "update" && current.targetId === targetId) {
5179
+ current.steps.push(entry);
5180
+ current.applied = applied;
5181
+ if (isCurrent) current.isCurrent = true;
5182
+ if (targetName) current.targetName = targetName;
5183
+ } else {
5184
+ current = {
5185
+ kind: "page",
5186
+ pageId,
5187
+ opType: step.opType,
5188
+ targetId: mergeable ? targetId : void 0,
5189
+ targetName,
5190
+ steps: [entry],
5191
+ applied,
5192
+ isCurrent
5193
+ };
5194
+ groups.push(current);
5195
+ }
5196
+ });
5197
+ return groups;
5198
+ };
5199
+ /**
5200
+ * 解析 StepValue 中的"目标节点 id"用于合并:
5201
+ * - 单节点 update:取唯一一项 updatedItems 的节点 id;
5202
+ * - 其它情形(多节点 update / add / remove):返回 undefined,表示不参与合并。
5203
+ */
5204
+ var detectPageTargetId = (step) => {
5205
+ if (step.opType !== "update") return void 0;
5206
+ const items = step.diff;
5207
+ if (items?.length !== 1) return void 0;
5208
+ return items[0].newSchema?.id ?? items[0].oldSchema?.id;
5209
+ };
5210
+ /** 解析 StepValue 中的目标节点可读名(用于 UI 展示)。 */
5211
+ var detectPageTargetName = (step) => {
5212
+ const items = step.diff;
5213
+ if (step.opType === "update") {
5214
+ if (items?.length === 1) {
5215
+ const node = items[0].newSchema || items[0].oldSchema;
5216
+ return node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : void 0);
5217
+ }
5218
+ return items?.length ? `${items.length} 个节点` : void 0;
5110
5219
  }
5111
- /**
5112
- * 解析 StepValue 中的"目标节点 id"用于合并:
5113
- * - 单节点 update:取唯一一项 updatedItems 的节点 id;
5114
- * - 其它情形(多节点 update / add / remove):返回 undefined,表示不参与合并。
5115
- */
5116
- static detectPageTargetId(step) {
5117
- if (step.opType !== "update") return void 0;
5118
- const items = step.updatedItems;
5119
- if (items?.length !== 1) return void 0;
5120
- return items[0].newNode?.id ?? items[0].oldNode?.id;
5121
- }
5122
- /** 解析 StepValue 中的目标节点可读名(用于 UI 展示)。 */
5123
- static detectPageTargetName(step) {
5124
- if (step.opType === "update") {
5125
- const items = step.updatedItems;
5126
- if (items?.length === 1) {
5127
- const node = items[0].newNode || items[0].oldNode;
5128
- return node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : void 0);
5129
- }
5130
- return items?.length ? `${items.length} 个节点` : void 0;
5220
+ if (step.opType === "add") {
5221
+ if (items?.length === 1) {
5222
+ const n = items[0].newSchema;
5223
+ return n?.name || n?.type || `${n?.id}`;
5131
5224
  }
5132
- if (step.opType === "add") {
5133
- if (step.nodes?.length === 1) {
5134
- const n = step.nodes[0];
5135
- return n.name || n.type || `${n.id}`;
5136
- }
5137
- return step.nodes?.length ? `${step.nodes.length} 个节点` : void 0;
5225
+ return items?.length ? `${items.length} 个节点` : void 0;
5226
+ }
5227
+ if (step.opType === "remove") {
5228
+ if (items?.length === 1) {
5229
+ const n = items[0].oldSchema;
5230
+ return n?.name || n?.type || `${n?.id}`;
5138
5231
  }
5139
- if (step.opType === "remove") {
5140
- if (step.removedItems?.length === 1) {
5141
- const n = step.removedItems[0].node;
5142
- return n.name || n.type || `${n.id}`;
5143
- }
5144
- return step.removedItems?.length ? `${step.removedItems.length} 个节点` : void 0;
5232
+ return items?.length ? `${items.length} 个节点` : void 0;
5233
+ }
5234
+ };
5235
+ /** `Record<Id, UndoRedo>` 整体序列化为 `Record<Id, SerializedUndoRedo>`。 */
5236
+ var serializeStacks = (stacks) => {
5237
+ const result = {};
5238
+ Object.entries(stacks).forEach(([id, undoRedo]) => {
5239
+ if (undoRedo) result[id] = undoRedo.serialize();
5240
+ });
5241
+ return result;
5242
+ };
5243
+ /**
5244
+ * 把 `Record<Id, SerializedUndoRedo>` 整体还原为 `Record<Id, UndoRedo>`。
5245
+ * 还原时把每个栈的游标定位到最近一条已保存(`saved === true`)记录之后。
5246
+ */
5247
+ var deserializeStacks = (stacks = {}) => {
5248
+ const result = {};
5249
+ Object.entries(stacks).forEach(([id, serialized]) => {
5250
+ if (serialized) result[id] = UndoRedo.fromSerialized(serialized, { isSavedStep: (element) => element.saved === true });
5251
+ });
5252
+ return result;
5253
+ };
5254
+ /**
5255
+ * 按 id 从「按 id 分栈」的记录表(代码块 / 数据源)中获取(或创建)对应的 UndoRedo 栈。
5256
+ */
5257
+ var getOrCreateStack = (stacks, id) => {
5258
+ if (!stacks[id]) stacks[id] = new UndoRedo();
5259
+ return stacks[id];
5260
+ };
5261
+ /**
5262
+ * 撤销下限:当页面栈 index 0 是 `opType: 'initial'` 的基线 step 时为 1(基线不可被撤销),否则为 0。
5263
+ * 用于把 cursor 钉在基线之上,保证 undo / canUndo / goto 都不会越过初始基线。
5264
+ */
5265
+ var undoFloor = (undoRedo) => {
5266
+ return undoRedo.getElementList()[0]?.opType === "initial" ? 1 : 0;
5267
+ };
5268
+ //#endregion
5269
+ //#region packages/editor/src/utils/indexed-db.ts
5270
+ /**
5271
+ * 一组极简的、基于原生 IndexedDB 的 Promise KV 工具,避免引入额外依赖。
5272
+ * 仅用于浏览器环境;在不支持 IndexedDB 的环境(如 SSR / 部分测试环境)下会 reject。
5273
+ */
5274
+ /** 是否处于支持 IndexedDB 的环境。 */
5275
+ var isIndexedDBSupported = () => typeof indexedDB !== "undefined" && indexedDB !== null;
5276
+ /**
5277
+ * 打开(必要时升级)数据库,确保目标 objectStore 存在后返回连接。
5278
+ *
5279
+ * 由于 objectStore 只能在 `onupgradeneeded` 内创建,这里先以当前版本打开,
5280
+ * 若发现 store 不存在则关闭连接、以更高版本重开来按需创建,兼容动态 storeName。
5281
+ */
5282
+ var openIndexedDB = (dbName, storeName) => new Promise((resolve, reject) => {
5283
+ if (!isIndexedDBSupported()) {
5284
+ reject(/* @__PURE__ */ new Error("当前环境不支持 IndexedDB"));
5285
+ return;
5286
+ }
5287
+ const request = indexedDB.open(dbName);
5288
+ request.onupgradeneeded = () => {
5289
+ const db = request.result;
5290
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
5291
+ };
5292
+ request.onerror = () => reject(request.error);
5293
+ request.onsuccess = () => {
5294
+ const db = request.result;
5295
+ if (db.objectStoreNames.contains(storeName)) {
5296
+ resolve(db);
5297
+ return;
5145
5298
  }
5299
+ const nextVersion = db.version + 1;
5300
+ db.close();
5301
+ const upgradeRequest = indexedDB.open(dbName, nextVersion);
5302
+ upgradeRequest.onupgradeneeded = () => {
5303
+ const upgradeDb = upgradeRequest.result;
5304
+ if (!upgradeDb.objectStoreNames.contains(storeName)) upgradeDb.createObjectStore(storeName);
5305
+ };
5306
+ upgradeRequest.onerror = () => reject(upgradeRequest.error);
5307
+ upgradeRequest.onsuccess = () => resolve(upgradeRequest.result);
5308
+ };
5309
+ });
5310
+ /** 写入(覆盖)一条记录。value 通过结构化克隆存储,支持 Map / Set 等结构。 */
5311
+ var idbSet = async (dbName, storeName, key, value) => {
5312
+ const db = await openIndexedDB(dbName, storeName);
5313
+ try {
5314
+ await new Promise((resolve, reject) => {
5315
+ const tx = db.transaction(storeName, "readwrite");
5316
+ tx.objectStore(storeName).put(value, key);
5317
+ tx.oncomplete = () => resolve();
5318
+ tx.onabort = () => reject(tx.error);
5319
+ tx.onerror = () => reject(tx.error);
5320
+ });
5321
+ } finally {
5322
+ db.close();
5323
+ }
5324
+ };
5325
+ /** 读取一条记录,不存在时返回 undefined。 */
5326
+ var idbGet = async (dbName, storeName, key) => {
5327
+ const db = await openIndexedDB(dbName, storeName);
5328
+ try {
5329
+ return await new Promise((resolve, reject) => {
5330
+ const request = db.transaction(storeName, "readonly").objectStore(storeName).get(key);
5331
+ request.onsuccess = () => resolve(request.result);
5332
+ request.onerror = () => reject(request.error);
5333
+ });
5334
+ } finally {
5335
+ db.close();
5336
+ }
5337
+ };
5338
+ /** 删除一条记录。 */
5339
+ var idbDelete = async (dbName, storeName, key) => {
5340
+ const db = await openIndexedDB(dbName, storeName);
5341
+ try {
5342
+ await new Promise((resolve, reject) => {
5343
+ const tx = db.transaction(storeName, "readwrite");
5344
+ tx.objectStore(storeName).delete(key);
5345
+ tx.oncomplete = () => resolve();
5346
+ tx.onabort = () => reject(tx.error);
5347
+ tx.onerror = () => reject(tx.error);
5348
+ });
5349
+ } finally {
5350
+ db.close();
5146
5351
  }
5352
+ };
5353
+ //#endregion
5354
+ //#region packages/editor/src/services/history.ts
5355
+ /** 历史记录持久化快照的默认存储位置与结构版本。 */
5356
+ var DEFAULT_DB_NAME = "tmagic-editor";
5357
+ var DEFAULT_STORE_NAME = "history";
5358
+ var DEFAULT_KEY = "default";
5359
+ var PERSIST_VERSION = 1;
5360
+ var History = class extends BaseService {
5147
5361
  state = (0, vue.reactive)({
5148
5362
  pageSteps: {},
5149
5363
  pageId: void 0,
@@ -5183,6 +5397,53 @@
5183
5397
  this.state.dataSourceState = {};
5184
5398
  }
5185
5399
  /**
5400
+ * 为指定页面 / 页面片种入一条「初始基线」记录(如加载 DSL 时的「初始 / 加载」基线)。
5401
+ *
5402
+ * 该记录是一条 `opType: 'initial'` 的 {@link StepValue},作为页面历史栈 **index 0 的固定底线**:
5403
+ * - 它是一条真实入栈的 step(随栈一起持久化),但被钉为撤销/回滚的下限——cursor 永不低于它,
5404
+ * 因此不会被 undo / goto / revert 触达(详见 {@link undo} / {@link setCanUndoRedo});
5405
+ * - 历史面板把它过滤出分组列表(见 {@link getPageHistoryGroups}),改由底部「初始」行展示。
5406
+ *
5407
+ * 仅当目标页面栈为空时种入(保证 initial 一定位于 index 0);已存在 initial 底线时默认不重复种入,
5408
+ * 传 `force=true` 且栈为空时按新基线种入。
5409
+ */
5410
+ setPageMarker(pageId, options = {}) {
5411
+ if (pageId === void 0 || pageId === null || `${pageId}` === "") return null;
5412
+ const existing = this.getPageMarker(pageId);
5413
+ if (existing) return existing;
5414
+ const stack = getOrCreateStack(this.state.pageSteps, pageId);
5415
+ if (stack.getLength() > 0) return null;
5416
+ const marker = {
5417
+ uuid: (0, _tmagic_utils.guid)(),
5418
+ opType: "initial",
5419
+ diff: [],
5420
+ data: {
5421
+ name: options.name || "",
5422
+ id: pageId
5423
+ },
5424
+ selectedBefore: [],
5425
+ selectedAfter: [],
5426
+ modifiedNodeIds: /* @__PURE__ */ new Map(),
5427
+ historyDescription: options.description || "未修改的初始状态",
5428
+ timestamp: Date.now(),
5429
+ ...options.source ? { source: options.source } : {}
5430
+ };
5431
+ stack.pushElement(marker);
5432
+ if (`${pageId}` === `${this.state.pageId}`) this.setCanUndoRedo();
5433
+ this.emit("page-marker-change", marker);
5434
+ return marker;
5435
+ }
5436
+ /**
5437
+ * 读取指定页面(缺省当前活动页)的初始基线 step(页面栈 index 0 且 `opType: 'initial'`);
5438
+ * 不存在时返回 undefined。
5439
+ */
5440
+ getPageMarker(pageId) {
5441
+ const targetPageId = pageId ?? this.state.pageId;
5442
+ if (!targetPageId) return void 0;
5443
+ const first = this.state.pageSteps[targetPageId]?.getElementList()[0];
5444
+ return first?.opType === "initial" ? first : void 0;
5445
+ }
5446
+ /**
5186
5447
  * 把一条步骤推入指定页面的栈;不指定 pageId 时落到当前活动页。
5187
5448
  *
5188
5449
  * 跨页操作(例如 `moveToContainer` 把节点搬到其它页)必须显式传入 `pageId`,
@@ -5191,11 +5452,31 @@
5191
5452
  push(state, pageId) {
5192
5453
  const undoRedo = this.getUndoRedo(pageId);
5193
5454
  if (!undoRedo) return null;
5455
+ if (state.uuid === void 0) state.uuid = (0, _tmagic_utils.guid)();
5194
5456
  if (state.timestamp === void 0) state.timestamp = Date.now();
5195
5457
  undoRedo.pushElement(state);
5196
5458
  if (pageId === void 0 || `${pageId}` === `${this.state.pageId}`) this.emit("change", state);
5197
5459
  return state;
5198
5460
  }
5461
+ /** 读取指定页面(缺省当前活动页)历史栈当前游标所在的 step(cursor - 1);无则返回 null。 */
5462
+ getCurrentPageStep(pageId) {
5463
+ const targetPageId = pageId ?? this.state.pageId;
5464
+ if (!targetPageId) return null;
5465
+ return this.state.pageSteps[targetPageId]?.getCurrentElement() ?? null;
5466
+ }
5467
+ /**
5468
+ * 用 `state` 替换指定页面栈当前游标所在的 step(并丢弃其后的重做尾部),游标不变。
5469
+ * 用于「连续 set root 记录合并」等就地替换最新一条的场景;替换成功后按需刷新 / 通知。
5470
+ */
5471
+ replaceCurrentPageStep(state, pageId) {
5472
+ const undoRedo = this.getUndoRedo(pageId);
5473
+ if (!undoRedo) return null;
5474
+ if (state.uuid === void 0) state.uuid = (0, _tmagic_utils.guid)();
5475
+ if (state.timestamp === void 0) state.timestamp = Date.now();
5476
+ if (!undoRedo.replaceCurrentElement(state)) return null;
5477
+ this.emit("change", state);
5478
+ return state;
5479
+ }
5199
5480
  /**
5200
5481
  * 推入一条代码块变更记录(与页面/节点完全无关),按 `codeBlockId` 维度独立一份 UndoRedo 栈。
5201
5482
  *
@@ -5206,16 +5487,15 @@
5206
5487
  * - 不直接驱动 codeBlockService,调用方负责实际写回。
5207
5488
  */
5208
5489
  pushCodeBlock(codeBlockId, payload) {
5209
- if (!codeBlockId) return null;
5210
- const step = {
5211
- id: codeBlockId,
5212
- oldContent: payload.oldContent ? cloneDeep$1(payload.oldContent) : null,
5213
- newContent: payload.newContent ? cloneDeep$1(payload.newContent) : null,
5214
- changeRecords: payload.changeRecords?.length ? cloneDeep$1(payload.changeRecords) : void 0,
5490
+ const step = createStackStep(codeBlockId, {
5491
+ oldValue: payload.oldContent,
5492
+ newValue: payload.newContent,
5493
+ changeRecords: payload.changeRecords,
5215
5494
  historyDescription: payload.historyDescription,
5216
- timestamp: Date.now()
5217
- };
5218
- this.getCodeBlockUndoRedo(codeBlockId).pushElement(step);
5495
+ source: payload.source
5496
+ });
5497
+ if (!step) return null;
5498
+ getOrCreateStack(this.state.codeBlockState, codeBlockId).pushElement(step);
5219
5499
  this.emit("code-block-history-change", codeBlockId, step);
5220
5500
  return step;
5221
5501
  }
@@ -5224,16 +5504,15 @@
5224
5504
  * 行为同 pushCodeBlock(新增 oldSchema=null;删除 newSchema=null)。
5225
5505
  */
5226
5506
  pushDataSource(dataSourceId, payload) {
5227
- if (!dataSourceId) return null;
5228
- const step = {
5229
- id: dataSourceId,
5230
- oldSchema: payload.oldSchema ? cloneDeep$1(payload.oldSchema) : null,
5231
- newSchema: payload.newSchema ? cloneDeep$1(payload.newSchema) : null,
5232
- changeRecords: payload.changeRecords?.length ? cloneDeep$1(payload.changeRecords) : void 0,
5507
+ const step = createStackStep(dataSourceId, {
5508
+ oldValue: payload.oldSchema,
5509
+ newValue: payload.newSchema,
5510
+ changeRecords: payload.changeRecords,
5233
5511
  historyDescription: payload.historyDescription,
5234
- timestamp: Date.now()
5235
- };
5236
- this.getDataSourceUndoRedo(dataSourceId).pushElement(step);
5512
+ source: payload.source
5513
+ });
5514
+ if (!step) return null;
5515
+ getOrCreateStack(this.state.dataSourceState, dataSourceId).pushElement(step);
5237
5516
  this.emit("data-source-history-change", dataSourceId, step);
5238
5517
  return step;
5239
5518
  }
@@ -5288,6 +5567,7 @@
5288
5567
  undo() {
5289
5568
  const undoRedo = this.getUndoRedo();
5290
5569
  if (!undoRedo) return null;
5570
+ if (undoRedo.getCursor() <= undoFloor(undoRedo)) return null;
5291
5571
  const state = undoRedo.undo();
5292
5572
  this.emit("change", state);
5293
5573
  return state;
@@ -5305,6 +5585,127 @@
5305
5585
  this.removeAllPlugins();
5306
5586
  }
5307
5587
  /**
5588
+ * 清空指定页面(缺省当前活动页)的历史记录栈。
5589
+ * 仅删除撤销/重做记录,不会改动当前 DSL;清空后该页将无法再撤销/重做之前的操作。
5590
+ */
5591
+ clearPage(pageId) {
5592
+ const targetPageId = pageId ?? this.state.pageId;
5593
+ if (!targetPageId) return;
5594
+ const marker = this.getPageMarker(targetPageId);
5595
+ this.state.pageSteps[targetPageId] = new UndoRedo();
5596
+ if (marker) this.setPageMarker(targetPageId, {
5597
+ name: marker.data?.name,
5598
+ description: marker.historyDescription,
5599
+ source: marker.source
5600
+ });
5601
+ if (`${targetPageId}` === `${this.state.pageId}`) {
5602
+ this.setCanUndoRedo();
5603
+ this.emit("clear-page", null);
5604
+ }
5605
+ }
5606
+ /**
5607
+ * 清空数据源历史记录栈:传入 `dataSourceId` 仅清空该数据源,缺省清空全部数据源。
5608
+ * 仅删除撤销/重做记录,不会改动数据源本身。
5609
+ */
5610
+ clearDataSource(dataSourceId) {
5611
+ if (dataSourceId !== void 0) delete this.state.dataSourceState[dataSourceId];
5612
+ else this.state.dataSourceState = {};
5613
+ }
5614
+ /**
5615
+ * 清空代码块历史记录栈:传入 `codeBlockId` 仅清空该代码块,缺省清空全部代码块。
5616
+ * 仅删除撤销/重做记录,不会改动代码块本身。
5617
+ */
5618
+ clearCodeBlock(codeBlockId) {
5619
+ if (codeBlockId !== void 0) delete this.state.codeBlockState[codeBlockId];
5620
+ else this.state.codeBlockState = {};
5621
+ }
5622
+ /**
5623
+ * 标记「整份 DSL 已保存」:把页面 / 代码块 / 数据源所有栈当前游标所在的记录都标为 `saved`。
5624
+ * 适用于「整体落库」场景;若只保存了其中一类,请改用更细粒度的
5625
+ * {@link markPageSaved} / {@link markCodeBlockSaved} / {@link markDataSourceSaved}。
5626
+ */
5627
+ markSaved() {
5628
+ Object.values(this.state.pageSteps).forEach(markStackSaved);
5629
+ Object.values(this.state.codeBlockState).forEach(markStackSaved);
5630
+ Object.values(this.state.dataSourceState).forEach(markStackSaved);
5631
+ this.emit("mark-saved", { kind: "all" });
5632
+ }
5633
+ /**
5634
+ * 标记指定页面(缺省为当前活动页)的历史栈当前记录为已保存。
5635
+ * 仅影响该页面自己的栈,不波及代码块 / 数据源 / 其它页面。
5636
+ */
5637
+ markPageSaved(pageId) {
5638
+ const targetPageId = pageId ?? this.state.pageId;
5639
+ if (!targetPageId) return;
5640
+ markStackSaved(this.state.pageSteps[targetPageId]);
5641
+ this.emit("mark-saved", {
5642
+ kind: "page",
5643
+ id: targetPageId
5644
+ });
5645
+ }
5646
+ /** 标记指定代码块的历史栈当前记录为已保存,仅影响该代码块自己的栈。 */
5647
+ markCodeBlockSaved(codeBlockId) {
5648
+ if (!codeBlockId) return;
5649
+ markStackSaved(this.state.codeBlockState[codeBlockId]);
5650
+ this.emit("mark-saved", {
5651
+ kind: "code-block",
5652
+ id: codeBlockId
5653
+ });
5654
+ }
5655
+ /** 标记指定数据源的历史栈当前记录为已保存,仅影响该数据源自己的栈。 */
5656
+ markDataSourceSaved(dataSourceId) {
5657
+ if (!dataSourceId) return;
5658
+ markStackSaved(this.state.dataSourceState[dataSourceId]);
5659
+ this.emit("mark-saved", {
5660
+ kind: "data-source",
5661
+ id: dataSourceId
5662
+ });
5663
+ }
5664
+ /**
5665
+ * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源)序列化后写入本地 IndexedDB。
5666
+ *
5667
+ * - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
5668
+ * - `key` 用于区分不同活动页 / 项目(同一 store 下可保存多份快照),缺省为 `default`;
5669
+ * - 返回写入成功的快照对象,便于调用方记录 savedAt 等信息;
5670
+ * - 不支持 IndexedDB 的环境(如 SSR)会 reject。
5671
+ */
5672
+ async saveToIndexedDB(options = {}) {
5673
+ const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
5674
+ const snapshot = {
5675
+ version: PERSIST_VERSION,
5676
+ pageId: this.state.pageId,
5677
+ pageSteps: serializeStacks(this.state.pageSteps),
5678
+ codeBlockState: serializeStacks(this.state.codeBlockState),
5679
+ dataSourceState: serializeStacks(this.state.dataSourceState),
5680
+ savedAt: Date.now()
5681
+ };
5682
+ await idbSet(this.resolveDbName(dbName, appId), storeName, key, (0, serialize_javascript.default)(snapshot));
5683
+ this.emit("save-to-indexed-db", snapshot);
5684
+ return snapshot;
5685
+ }
5686
+ /**
5687
+ * 从本地 IndexedDB 读取此前保存的历史快照并重建全部撤销/重做栈。
5688
+ *
5689
+ * - 读取到的每个栈都会经 {@link UndoRedo.fromSerialized} 还原(含游标),随后可直接 undo/redo;
5690
+ * - 会整体覆盖当前内存中的历史状态,并把活动页恢复为快照中的 pageId;
5691
+ * - 找不到对应记录时返回 null,且不改动当前状态;
5692
+ * - 不支持 IndexedDB 的环境(如 SSR)会 reject。
5693
+ */
5694
+ async restoreFromIndexedDB(options = {}) {
5695
+ const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
5696
+ const raw = await idbGet(this.resolveDbName(dbName, appId), storeName, key);
5697
+ if (!raw) return null;
5698
+ const snapshot = typeof raw === "string" ? getEditorConfig("parseDSL")(`(${raw})`) : raw;
5699
+ if (!snapshot) return null;
5700
+ this.state.pageSteps = deserializeStacks(snapshot.pageSteps);
5701
+ this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState);
5702
+ this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState);
5703
+ this.state.pageId = snapshot.pageId;
5704
+ this.setCanUndoRedo();
5705
+ this.emit("restore-from-indexed-db", snapshot);
5706
+ return snapshot;
5707
+ }
5708
+ /**
5308
5709
  * 取出当前活动页的历史步骤平铺列表(包含已应用 + 已撤销)。
5309
5710
  * 列表按时间正序,最早一步在最前面。
5310
5711
  * 通常 UI 应使用 `getPageHistoryGroups` 取已合并分组的版本;本方法仅为兼容/调试保留。
@@ -5335,8 +5736,7 @@
5335
5736
  if (!undoRedo) return [];
5336
5737
  const list = undoRedo.getElementList();
5337
5738
  if (!list.length) return [];
5338
- const cursor = undoRedo.getCursor();
5339
- return History.mergePageSteps(targetPageId, list, cursor);
5739
+ return mergePageSteps(targetPageId, list, undoRedo.getCursor()).filter((group) => group.opType !== "initial");
5340
5740
  }
5341
5741
  /**
5342
5742
  * 取出全部代码块的历史栈,按 codeBlockId 分组。
@@ -5352,7 +5752,7 @@
5352
5752
  const list = undoRedo.getElementList();
5353
5753
  if (!list.length) return;
5354
5754
  const cursor = undoRedo.getCursor();
5355
- groups.push(...History.mergeCodeBlockSteps(id, list, cursor));
5755
+ groups.push(...mergeStackSteps("code-block", id, list, cursor));
5356
5756
  });
5357
5757
  return groups;
5358
5758
  }
@@ -5402,6 +5802,44 @@
5402
5802
  }));
5403
5803
  }
5404
5804
  /**
5805
+ * 按历史记录 uuid 在指定页面(默认当前活动页)的栈中查找其索引。
5806
+ * 找不到时返回 -1。供「按 uuid 回滚」等需要把 uuid 映射回 index 的场景使用。
5807
+ */
5808
+ getPageStepIndexByUuid(uuid, pageId) {
5809
+ if (!uuid) return -1;
5810
+ return this.getPageStepList(pageId).findIndex((entry) => entry.step.uuid === uuid);
5811
+ }
5812
+ /**
5813
+ * 按历史记录 uuid 在全部代码块栈中查找其所属 codeBlockId 与索引。
5814
+ * 找不到时返回 null。
5815
+ */
5816
+ findCodeBlockStepLocationByUuid(uuid) {
5817
+ if (!uuid) return null;
5818
+ for (const id of Object.keys(this.state.codeBlockState)) {
5819
+ const index = this.getCodeBlockStepList(id).findIndex((entry) => entry.step.uuid === uuid);
5820
+ if (index >= 0) return {
5821
+ id,
5822
+ index
5823
+ };
5824
+ }
5825
+ return null;
5826
+ }
5827
+ /**
5828
+ * 按历史记录 uuid 在全部数据源栈中查找其所属 dataSourceId 与索引。
5829
+ * 找不到时返回 null。
5830
+ */
5831
+ findDataSourceStepLocationByUuid(uuid) {
5832
+ if (!uuid) return null;
5833
+ for (const id of Object.keys(this.state.dataSourceState)) {
5834
+ const index = this.getDataSourceStepList(id).findIndex((entry) => entry.step.uuid === uuid);
5835
+ if (index >= 0) return {
5836
+ id,
5837
+ index
5838
+ };
5839
+ }
5840
+ return null;
5841
+ }
5842
+ /**
5405
5843
  * 取出全部数据源的历史栈,按 dataSourceId 分组。同上。
5406
5844
  */
5407
5845
  getDataSourceHistoryGroups() {
@@ -5411,7 +5849,7 @@
5411
5849
  const list = undoRedo.getElementList();
5412
5850
  if (!list.length) return;
5413
5851
  const cursor = undoRedo.getCursor();
5414
- groups.push(...History.mergeDataSourceSteps(id, list, cursor));
5852
+ groups.push(...mergeStackSteps("data-source", id, list, cursor));
5415
5853
  });
5416
5854
  return groups;
5417
5855
  }
@@ -5427,35 +5865,22 @@
5427
5865
  if (!this.state.pageSteps[targetPageId]) this.state.pageSteps[targetPageId] = new UndoRedo();
5428
5866
  return this.state.pageSteps[targetPageId];
5429
5867
  }
5430
- setCanUndoRedo() {
5431
- const undoRedo = this.getUndoRedo();
5432
- this.state.canRedo = undoRedo?.canRedo() || false;
5433
- this.state.canUndo = undoRedo?.canUndo() || false;
5434
- }
5435
5868
  /**
5436
- * id 获取(或创建)指定代码块的 UndoRedo 栈。
5869
+ * 把基础 dbName 与当前 DSL(root app)的 id 拼成最终库名,实现不同应用历史隔离。
5870
+ * 取不到 app id(如尚未加载 DSL)时退回基础 dbName。
5437
5871
  */
5438
- getCodeBlockUndoRedo(codeBlockId) {
5439
- if (!this.state.codeBlockState[codeBlockId]) this.state.codeBlockState[codeBlockId] = new UndoRedo();
5440
- return this.state.codeBlockState[codeBlockId];
5872
+ resolveDbName(dbName, appId) {
5873
+ const resolvedAppId = appId ?? editor_default.get("root")?.id;
5874
+ return resolvedAppId ? `${dbName}-${resolvedAppId}` : dbName;
5441
5875
  }
5442
- /**
5443
- * id 获取(或创建)指定数据源的 UndoRedo 栈。
5444
- */
5445
- getDataSourceUndoRedo(dataSourceId) {
5446
- if (!this.state.dataSourceState[dataSourceId]) this.state.dataSourceState[dataSourceId] = new UndoRedo();
5447
- return this.state.dataSourceState[dataSourceId];
5876
+ setCanUndoRedo() {
5877
+ const undoRedo = this.getUndoRedo();
5878
+ this.state.canRedo = undoRedo?.canRedo() || false;
5879
+ this.state.canUndo = undoRedo ? undoRedo.getCursor() > undoFloor(undoRedo) : false;
5448
5880
  }
5449
5881
  };
5450
5882
  var history_default = new History();
5451
5883
  //#endregion
5452
- //#region packages/editor/src/utils/config.ts
5453
- var $TMAGIC_EDITOR = {};
5454
- var setEditorConfig = (option) => {
5455
- $TMAGIC_EDITOR = option;
5456
- };
5457
- var getEditorConfig = (key) => $TMAGIC_EDITOR[key];
5458
- //#endregion
5459
5884
  //#region packages/editor/src/services/storage.ts
5460
5885
  var Protocol = /* @__PURE__ */ function(Protocol) {
5461
5886
  Protocol["OBJECT"] = "object";
@@ -6005,6 +6430,40 @@
6005
6430
  aborted: false
6006
6431
  };
6007
6432
  };
6433
+ /**
6434
+ * 给「回滚」生成的新 step 用的简短描述生成器。
6435
+ * 与 UI 层 `describePageStep` 同义,但避免 service 反向依赖 layouts/,故放在此工具函数中。
6436
+ */
6437
+ var describeStepForRevert = (step) => {
6438
+ const items = step.diff ?? [];
6439
+ const withId = (node, label) => {
6440
+ const id = node?.id;
6441
+ if (id === void 0 || id === null || `${id}` === "") return label;
6442
+ return label ? `${label}(id: ${id})` : `id: ${id}`;
6443
+ };
6444
+ switch (step.opType) {
6445
+ case "add": {
6446
+ const count = items.length;
6447
+ const node = items[0]?.newSchema;
6448
+ const label = node?.name || node?.type || "";
6449
+ return `撤回新增 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ""}`;
6450
+ }
6451
+ case "remove": {
6452
+ const count = items.length;
6453
+ const node = items[0]?.oldSchema;
6454
+ const label = node?.name || node?.type || "";
6455
+ return `还原已删除的 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ""}`;
6456
+ }
6457
+ default:
6458
+ if (items.length === 1) {
6459
+ const { newSchema, oldSchema, changeRecords } = items[0];
6460
+ const target = withId(newSchema || oldSchema, newSchema?.name || newSchema?.type || oldSchema?.name || oldSchema?.type || "");
6461
+ const propPath = changeRecords?.[0]?.propPath;
6462
+ return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
6463
+ }
6464
+ return `还原 ${items.length} 个节点的修改`;
6465
+ }
6466
+ };
6008
6467
  //#endregion
6009
6468
  //#region packages/editor/src/utils/operator.ts
6010
6469
  /**
@@ -6073,35 +6532,15 @@
6073
6532
  //#endregion
6074
6533
  //#region packages/editor/src/services/editor.ts
6075
6534
  /**
6076
- * 给「回滚」生成的新 step 用的简短描述生成器。
6077
- * UI `describePageStep` 同义,但避免 service 反向依赖 layouts/,故在此本地实现。
6535
+ * 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
6536
+ * `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
6537
+ * 缺省(未传 / 空数组)才退化为整节点替换。
6078
6538
  */
6079
- var describeStepForRevert = (step) => {
6080
- switch (step.opType) {
6081
- case "add": {
6082
- const count = step.nodes?.length ?? 0;
6083
- const node = step.nodes?.[0];
6084
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
6085
- return `撤回新增 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
6086
- }
6087
- case "remove": {
6088
- const count = step.removedItems?.length ?? 0;
6089
- const node = step.removedItems?.[0]?.node;
6090
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
6091
- return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
6092
- }
6093
- default: {
6094
- const items = step.updatedItems ?? [];
6095
- if (items.length === 1) {
6096
- const { newNode, oldNode, changeRecords } = items[0];
6097
- const target = newNode?.name || newNode?.type || oldNode?.name || oldNode?.type || `${newNode?.id ?? ""}`;
6098
- const propPath = changeRecords?.[0]?.propPath;
6099
- return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
6100
- }
6101
- return `还原 ${items.length} 个节点的修改`;
6102
- }
6103
- }
6104
- };
6539
+ var buildUpdateDiff = (items) => items.map(({ oldNode, newNode, changeRecords }) => ({
6540
+ oldSchema: oldNode,
6541
+ newSchema: newNode,
6542
+ ...changeRecords?.length ? { changeRecords } : {}
6543
+ }));
6105
6544
  var Editor = class extends BaseService {
6106
6545
  state = (0, vue.reactive)({
6107
6546
  root: null,
@@ -6119,6 +6558,12 @@
6119
6558
  alwaysMultiSelect: false
6120
6559
  });
6121
6560
  selectionBeforeOp = null;
6561
+ /**
6562
+ * 最近一次 pushOpHistory 写入的历史记录 uuid。
6563
+ * 供 *AndGetHistoryId 系列方法在调用普通操作后取回本次产生的历史记录 id;
6564
+ * 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
6565
+ */
6566
+ lastPushedHistoryId = null;
6122
6567
  constructor() {
6123
6568
  super(canUsePluginMethods.async.map((methodName) => ({
6124
6569
  name: methodName,
@@ -6133,8 +6578,9 @@
6133
6578
  * 设置当前指点节点配置
6134
6579
  * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
6135
6580
  * @param value MNode
6581
+ * @param options.historySource 设置 root 时,本次变更写入历史记录的「操作来源」(仅 name === 'root' 时生效)
6136
6582
  */
6137
- set(name, value) {
6583
+ set(name, value, options = {}) {
6138
6584
  const preValue = this.state[name];
6139
6585
  this.state[name] = value;
6140
6586
  if (name === "nodes" && Array.isArray(value)) this.set("node", value[0]);
@@ -6145,6 +6591,13 @@
6145
6591
  this.state.pageLength = getPageList(app).length || 0;
6146
6592
  this.state.pageFragmentLength = getPageFragmentList(app).length || 0;
6147
6593
  this.state.stageLoading = this.state.pageLength !== 0;
6594
+ if (preValue && !isEmpty(preValue)) this.pushRootDiffHistory(preValue, app, options.historySource);
6595
+ else app.items?.forEach((pageNode) => {
6596
+ if (pageNode?.id !== void 0 && !history_default.getPageMarker(pageNode.id)) history_default.setPageMarker(pageNode.id, {
6597
+ name: pageNode.name,
6598
+ source: options.historySource
6599
+ });
6600
+ });
6148
6601
  } else {
6149
6602
  this.state.pageLength = 0;
6150
6603
  this.state.pageFragmentLength = 0;
@@ -6170,7 +6623,23 @@
6170
6623
  getNodeInfo(id, raw = true) {
6171
6624
  let root = this.get("root");
6172
6625
  if (raw) root = (0, vue.toRaw)(root);
6173
- return (0, _tmagic_utils.getNodeInfo)(id, root);
6626
+ if (!root) return {
6627
+ node: null,
6628
+ parent: null,
6629
+ page: null
6630
+ };
6631
+ if (id === root.id) return {
6632
+ node: root,
6633
+ parent: null,
6634
+ page: null
6635
+ };
6636
+ const pageIdStr = `${this.get("page")?.id || ""}`;
6637
+ const currentPageNode = root.items?.find((item) => `${item.id}` === pageIdStr);
6638
+ if (currentPageNode && `${id}` !== pageIdStr) {
6639
+ const info = (0, _tmagic_utils.getNodeInfo)(id, currentPageNode);
6640
+ if (info.node) return info;
6641
+ }
6642
+ return (0, _tmagic_utils.getNodeInfo)(id, root, currentPageNode);
6174
6643
  }
6175
6644
  /**
6176
6645
  * 根据ID获取指点节点配置
@@ -6330,7 +6799,7 @@
6330
6799
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6331
6800
  * @returns 添加后的节点
6332
6801
  */
6333
- async add(addNode, parent, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
6802
+ async add(addNode, parent, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
6334
6803
  this.captureSelectionBeforeOp();
6335
6804
  const stage = this.get("stage");
6336
6805
  const addNodes = [];
@@ -6363,18 +6832,26 @@
6363
6832
  }
6364
6833
  if (!((0, _tmagic_utils.isPage)(newNodes[0]) || (0, _tmagic_utils.isPageFragment)(newNodes[0]))) {
6365
6834
  const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
6366
- if (!doNotPushHistory) this.pushOpHistory("add", {
6367
- nodes: newNodes.map((n) => cloneDeep$1((0, vue.toRaw)(n))),
6368
- parentId: (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id,
6369
- indexMap: Object.fromEntries(newNodes.map((n) => {
6370
- const p = this.getParentById(n.id, false);
6371
- return [n.id, p ? getNodeIndex(n.id, p) : -1];
6372
- }))
6373
- }, {
6374
- name: pageForOp?.name || "",
6375
- id: pageForOp.id
6376
- }, historyDescription);
6377
- else this.selectionBeforeOp = null;
6835
+ if (!doNotPushHistory) {
6836
+ const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id;
6837
+ this.pushOpHistory("add", {
6838
+ diff: newNodes.map((n) => {
6839
+ const p = this.getParentById(n.id, false);
6840
+ const idx = p ? getNodeIndex(n.id, p) : -1;
6841
+ return {
6842
+ newSchema: cloneDeep$1((0, vue.toRaw)(n)),
6843
+ parentId,
6844
+ index: typeof idx === "number" ? idx : -1
6845
+ };
6846
+ }),
6847
+ pageData: {
6848
+ name: pageForOp?.name || "",
6849
+ id: pageForOp.id
6850
+ },
6851
+ historyDescription,
6852
+ source: historySource
6853
+ });
6854
+ } else this.selectionBeforeOp = null;
6378
6855
  }
6379
6856
  this.emit("add", newNodes);
6380
6857
  return Array.isArray(addNode) ? newNodes : newNodes[0];
@@ -6440,7 +6917,7 @@
6440
6917
  * @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
6441
6918
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6442
6919
  */
6443
- async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
6920
+ async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
6444
6921
  this.captureSelectionBeforeOp();
6445
6922
  const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
6446
6923
  const removedItems = [];
@@ -6454,7 +6931,7 @@
6454
6931
  };
6455
6932
  const idx = getNodeIndex(curNode.id, parent);
6456
6933
  removedItems.push({
6457
- node: cloneDeep$1((0, vue.toRaw)(curNode)),
6934
+ oldSchema: cloneDeep$1((0, vue.toRaw)(curNode)),
6458
6935
  parentId: parent.id,
6459
6936
  index: typeof idx === "number" ? idx : -1
6460
6937
  });
@@ -6464,11 +6941,16 @@
6464
6941
  doNotSelect,
6465
6942
  doNotSwitchPage
6466
6943
  })));
6467
- if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", { removedItems }, pageForOp, historyDescription);
6944
+ if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", {
6945
+ diff: removedItems,
6946
+ pageData: pageForOp,
6947
+ historyDescription,
6948
+ source: historySource
6949
+ });
6468
6950
  else this.selectionBeforeOp = null;
6469
6951
  this.emit("remove", nodes);
6470
6952
  }
6471
- async doUpdate(config, { changeRecords = [] } = {}) {
6953
+ async doUpdate(config, { changeRecords = [], historySource } = {}) {
6472
6954
  const root = this.get("root");
6473
6955
  if (!root) throw new Error("root为空");
6474
6956
  if (!config?.id) throw new Error("没有配置或者配置缺少id值");
@@ -6479,7 +6961,7 @@
6479
6961
  newConfig = mergeWith(cloneDeep$1(node), newConfig, editorNodeMergeCustomizer);
6480
6962
  if (!newConfig.type) throw new Error("配置缺少type值");
6481
6963
  if (newConfig.type === _tmagic_core.NodeType.ROOT) {
6482
- this.set("root", newConfig);
6964
+ this.set("root", newConfig, { historySource });
6483
6965
  return {
6484
6966
  oldNode: node,
6485
6967
  newNode: newConfig,
@@ -6525,23 +7007,31 @@
6525
7007
  */
6526
7008
  async update(config, data = {}) {
6527
7009
  this.captureSelectionBeforeOp();
6528
- const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription } = data;
7010
+ const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription, historySource } = data;
6529
7011
  const nodes = Array.isArray(config) ? config : [config];
6530
7012
  const updateData = await Promise.all(nodes.map((node, index) => {
6531
7013
  const recordsForNode = changeRecordList ? changeRecordList[index] ?? [] : changeRecords ?? [];
6532
- return this.doUpdate(node, { changeRecords: recordsForNode });
7014
+ return this.doUpdate(node, {
7015
+ changeRecords: recordsForNode,
7016
+ historySource
7017
+ });
6533
7018
  }));
6534
7019
  if (updateData[0].oldNode?.type !== _tmagic_core.NodeType.ROOT) {
6535
7020
  if (this.get("nodes").length) if (!doNotPushHistory) {
6536
7021
  const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
6537
- this.pushOpHistory("update", { updatedItems: updateData.map((d) => ({
6538
- oldNode: cloneDeep$1(d.oldNode),
6539
- newNode: cloneDeep$1((0, vue.toRaw)(d.newNode)),
6540
- changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
6541
- })) }, {
6542
- name: pageForOp?.name || "",
6543
- id: pageForOp.id
6544
- }, historyDescription);
7022
+ this.pushOpHistory("update", {
7023
+ diff: buildUpdateDiff(updateData.map((d) => ({
7024
+ oldNode: cloneDeep$1(d.oldNode),
7025
+ newNode: cloneDeep$1(d.newNode),
7026
+ changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
7027
+ }))),
7028
+ pageData: {
7029
+ name: pageForOp?.name || "",
7030
+ id: pageForOp.id
7031
+ },
7032
+ historyDescription,
7033
+ source: historySource
7034
+ });
6545
7035
  } else this.selectionBeforeOp = null;
6546
7036
  }
6547
7037
  this.emit("update", updateData);
@@ -6557,7 +7047,7 @@
6557
7047
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6558
7048
  * @returns void
6559
7049
  */
6560
- async sort(id1, id2, { doNotSelect = false, doNotPushHistory = false } = {}) {
7050
+ async sort(id1, id2, { doNotSelect = false, doNotPushHistory = false, historySource } = {}) {
6561
7051
  this.captureSelectionBeforeOp();
6562
7052
  const root = this.get("root");
6563
7053
  if (!root) throw new Error("root为空");
@@ -6569,7 +7059,10 @@
6569
7059
  if (index2 < 0) return;
6570
7060
  const index1 = parent.items.findIndex((node) => `${node.id}` === `${id1}`);
6571
7061
  parent.items.splice(index2, 0, ...parent.items.splice(index1, 1));
6572
- await this.update(parent, { doNotPushHistory });
7062
+ await this.update(parent, {
7063
+ doNotPushHistory,
7064
+ historySource
7065
+ });
6573
7066
  if (!doNotSelect) await this.select(node);
6574
7067
  this.get("stage")?.update({
6575
7068
  config: cloneDeep$1(node),
@@ -6605,7 +7098,7 @@
6605
7098
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6606
7099
  * @returns 添加后的组件节点配置
6607
7100
  */
6608
- async paste(position = {}, collectorOptions, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
7101
+ async paste(position = {}, collectorOptions, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
6609
7102
  const config = storage_default.getItem(COPY_STORAGE_KEY);
6610
7103
  if (!Array.isArray(config)) return;
6611
7104
  const node = this.get("node");
@@ -6619,7 +7112,9 @@
6619
7112
  return this.add(pasteConfigs, parent, {
6620
7113
  doNotSelect,
6621
7114
  doNotSwitchPage,
6622
- doNotPushHistory
7115
+ doNotPushHistory,
7116
+ historyDescription,
7117
+ historySource
6623
7118
  });
6624
7119
  }
6625
7120
  async doPaste(config, position = {}) {
@@ -6647,11 +7142,15 @@
6647
7142
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6648
7143
  * @returns 当前组件节点配置
6649
7144
  */
6650
- async alignCenter(config, { doNotSelect = false, doNotPushHistory = false } = {}) {
7145
+ async alignCenter(config, { doNotSelect = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
6651
7146
  const nodes = Array.isArray(config) ? config : [config];
6652
7147
  const stage = this.get("stage");
6653
7148
  const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
6654
- const newNode = await this.update(newNodes, { doNotPushHistory });
7149
+ const newNode = await this.update(newNodes, {
7150
+ doNotPushHistory,
7151
+ historyDescription,
7152
+ historySource
7153
+ });
6655
7154
  if (!doNotSelect) if (newNodes.length > 1) await stage?.multiSelect(newNodes.map((node) => node.id));
6656
7155
  else await stage?.select(newNodes[0].id);
6657
7156
  return newNode;
@@ -6662,7 +7161,7 @@
6662
7161
  * @param options 可选配置
6663
7162
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6664
7163
  */
6665
- async moveLayer(offset, { doNotPushHistory = false } = {}) {
7164
+ async moveLayer(offset, { doNotPushHistory = false, historyDescription, historySource } = {}) {
6666
7165
  this.captureSelectionBeforeOp();
6667
7166
  const root = this.get("root");
6668
7167
  if (!root) throw new Error("root为空");
@@ -6687,12 +7186,17 @@
6687
7186
  this.addModifiedNodeId(parent.id);
6688
7187
  if (!doNotPushHistory) {
6689
7188
  const pageForOp = this.getNodeInfo(node.id, false).page;
6690
- this.pushOpHistory("update", { updatedItems: [{
6691
- oldNode: oldParent,
6692
- newNode: cloneDeep$1((0, vue.toRaw)(parent))
6693
- }] }, {
6694
- name: pageForOp?.name || "",
6695
- id: pageForOp.id
7189
+ this.pushOpHistory("update", {
7190
+ diff: buildUpdateDiff([{
7191
+ oldNode: oldParent,
7192
+ newNode: cloneDeep$1((0, vue.toRaw)(parent))
7193
+ }]),
7194
+ pageData: {
7195
+ name: pageForOp?.name || "",
7196
+ id: pageForOp.id
7197
+ },
7198
+ historyDescription,
7199
+ source: historySource
6696
7200
  });
6697
7201
  } else this.selectionBeforeOp = null;
6698
7202
  this.emit("move-layer", offset);
@@ -6711,7 +7215,7 @@
6711
7215
  * @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
6712
7216
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
6713
7217
  */
6714
- async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
7218
+ async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
6715
7219
  const isBatch = Array.isArray(config);
6716
7220
  const configs = (isBatch ? config : [config]).filter((item) => !((0, _tmagic_utils.isPage)(item) || (0, _tmagic_utils.isPageFragment)(item)));
6717
7221
  if (configs.length === 0) throw new Error("没有可移动的节点");
@@ -6759,11 +7263,16 @@
6759
7263
  name: "",
6760
7264
  id: target.id
6761
7265
  };
6762
- this.pushOpHistory("update", { updatedItems }, historyPage);
7266
+ this.pushOpHistory("update", {
7267
+ diff: buildUpdateDiff(updatedItems),
7268
+ pageData: historyPage,
7269
+ historyDescription,
7270
+ source: historySource
7271
+ });
6763
7272
  } else this.selectionBeforeOp = null;
6764
7273
  return isBatch ? newConfigs : newConfigs[0];
6765
7274
  }
6766
- async dragTo(config, targetParent, targetIndex, { doNotPushHistory = false } = {}) {
7275
+ async dragTo(config, targetParent, targetIndex, { doNotPushHistory = false, historyDescription, historySource } = {}) {
6767
7276
  this.captureSelectionBeforeOp();
6768
7277
  if (!targetParent || !Array.isArray(targetParent.items)) return;
6769
7278
  const configs = Array.isArray(config) ? config : [config];
@@ -6805,9 +7314,14 @@
6805
7314
  }
6806
7315
  if (!doNotPushHistory) {
6807
7316
  const pageForOp = this.getNodeInfo(configs[0].id, false).page;
6808
- this.pushOpHistory("update", { updatedItems }, {
6809
- name: pageForOp?.name || "",
6810
- id: pageForOp.id
7317
+ this.pushOpHistory("update", {
7318
+ diff: buildUpdateDiff(updatedItems),
7319
+ pageData: {
7320
+ name: pageForOp?.name || "",
7321
+ id: pageForOp.id
7322
+ },
7323
+ historyDescription,
7324
+ source: historySource
6811
7325
  });
6812
7326
  } else this.selectionBeforeOp = null;
6813
7327
  this.emit("drag-to", {
@@ -6817,6 +7331,49 @@
6817
7331
  });
6818
7332
  }
6819
7333
  /**
7334
+ * 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
7335
+ * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link StepValue.uuid}),
7336
+ * 而非节点 / 节点数组。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
7337
+ *
7338
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时返回 null。
7339
+ */
7340
+ /** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7341
+ async addAndGetHistoryId(addNode, parent, options = {}) {
7342
+ this.lastPushedHistoryId = null;
7343
+ await this.add(addNode, parent, options);
7344
+ return this.lastPushedHistoryId;
7345
+ }
7346
+ /** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7347
+ async removeAndGetHistoryId(nodeOrNodeList, options = {}) {
7348
+ this.lastPushedHistoryId = null;
7349
+ await this.remove(nodeOrNodeList, options);
7350
+ return this.lastPushedHistoryId;
7351
+ }
7352
+ /** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7353
+ async updateAndGetHistoryId(config, data = {}) {
7354
+ this.lastPushedHistoryId = null;
7355
+ await this.update(config, data);
7356
+ return this.lastPushedHistoryId;
7357
+ }
7358
+ /** 等价于 {@link moveLayer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7359
+ async moveLayerAndGetHistoryId(offset, options = {}) {
7360
+ this.lastPushedHistoryId = null;
7361
+ await this.moveLayer(offset, options);
7362
+ return this.lastPushedHistoryId;
7363
+ }
7364
+ /** 等价于 {@link moveToContainer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7365
+ async moveToContainerAndGetHistoryId(config, targetId, options = {}) {
7366
+ this.lastPushedHistoryId = null;
7367
+ await this.moveToContainer(config, targetId, options);
7368
+ return this.lastPushedHistoryId;
7369
+ }
7370
+ /** 等价于 {@link dragTo},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7371
+ async dragToAndGetHistoryId(config, targetParent, targetIndex, options = {}) {
7372
+ this.lastPushedHistoryId = null;
7373
+ await this.dragTo(config, targetParent, targetIndex, options);
7374
+ return this.lastPushedHistoryId;
7375
+ }
7376
+ /**
6820
7377
  * 撤销当前操作
6821
7378
  * @returns 被撤销的操作
6822
7379
  */
@@ -6854,7 +7411,12 @@
6854
7411
  const entry = history_default.getPageStepList()[index];
6855
7412
  if (!entry?.applied) return null;
6856
7413
  const { step } = entry;
7414
+ if (step.opType === "initial") return null;
6857
7415
  if (!this.get("root")) return null;
7416
+ if (step.opType === "update") {
7417
+ const items = step.diff ?? [];
7418
+ if (!items.length || !items.every((item) => item.changeRecords?.length)) return null;
7419
+ }
6858
7420
  let revertedStep = null;
6859
7421
  const captureRevert = (s) => {
6860
7422
  revertedStep = s;
@@ -6864,28 +7426,31 @@
6864
7426
  const opts = {
6865
7427
  doNotSelect: true,
6866
7428
  doNotSwitchPage: true,
6867
- historyDescription
7429
+ historyDescription,
7430
+ historySource: "rollback"
6868
7431
  };
6869
7432
  try {
6870
7433
  switch (step.opType) {
6871
- case "add": {
6872
- const nodes = step.nodes ?? [];
6873
- for (const n of nodes) {
6874
- const existing = this.getNodeById(n.id, false);
7434
+ case "add":
7435
+ for (const { newSchema } of step.diff ?? []) {
7436
+ if (!newSchema) continue;
7437
+ const existing = this.getNodeById(newSchema.id, false);
6875
7438
  if (existing) await this.remove(existing, opts);
6876
7439
  }
6877
7440
  break;
6878
- }
6879
7441
  case "remove": {
6880
- const sorted = [...step.removedItems ?? []].sort((a, b) => a.index - b.index);
6881
- for (const { node, parentId } of sorted) {
7442
+ const sorted = [...step.diff ?? []].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7443
+ for (const { oldSchema, parentId } of sorted) {
7444
+ if (!oldSchema || parentId === void 0) continue;
6882
7445
  const parent = this.getNodeById(parentId, false);
6883
- if (parent) await this.add([cloneDeep$1(node)], parent, opts);
7446
+ if (parent) await this.add([cloneDeep$1(oldSchema)], parent, opts);
6884
7447
  }
6885
7448
  break;
6886
7449
  }
6887
7450
  case "update": {
6888
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
7451
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
7452
+ const oldNode = oldSchema;
7453
+ const newNode = newSchema;
6889
7454
  if (changeRecords?.length) {
6890
7455
  const patch = {
6891
7456
  id: newNode.id,
@@ -6900,7 +7465,10 @@
6900
7465
  }
6901
7466
  return cloneDeep$1(oldNode);
6902
7467
  });
6903
- if (configs.length) await this.update(configs, { historyDescription });
7468
+ if (configs.length) await this.update(configs, {
7469
+ historyDescription,
7470
+ historySource: "rollback"
7471
+ });
6904
7472
  break;
6905
7473
  }
6906
7474
  }
@@ -6912,6 +7480,19 @@
6912
7480
  return revertedStep;
6913
7481
  }
6914
7482
  /**
7483
+ * 通过历史记录 uuid 回滚当前页面的某条历史步骤,语义与 {@link revertPageStep} 完全一致,
7484
+ * 仅入参从 index 改为 uuid({@link StepValue.uuid})。uuid 不随栈内步骤增删而变化,
7485
+ * 更适合业务侧持有引用后再回滚(埋点、跨端同步等场景)。
7486
+ *
7487
+ * @param uuid 目标历史记录的 uuid,通常由 *AndGetHistoryId 方法返回
7488
+ * @returns 反向后产生的新 step;找不到对应 uuid / 未应用 / 反向失败时返回 null
7489
+ */
7490
+ async revertPageStepById(uuid) {
7491
+ const index = history_default.getPageStepIndexByUuid(uuid);
7492
+ if (index < 0) return null;
7493
+ return this.revertPageStep(index);
7494
+ }
7495
+ /**
6915
7496
  * 跳转当前页面历史栈到指定游标位置。
6916
7497
  *
6917
7498
  * `targetCursor` 与 `UndoRedo.getCursor()` 同义:表示"已应用步骤数量",
@@ -6934,7 +7515,7 @@
6934
7515
  }
6935
7516
  return cursor;
6936
7517
  }
6937
- async move(left, top, { doNotPushHistory = false } = {}) {
7518
+ async move(left, top, { doNotPushHistory = false, historyDescription, historySource } = {}) {
6938
7519
  const node = (0, vue.toRaw)(this.get("node"));
6939
7520
  if (!node || (0, _tmagic_utils.isPage)(node)) return;
6940
7521
  const newStyle = calcMoveStyle(node.style || {}, left, top);
@@ -6943,7 +7524,11 @@
6943
7524
  id: node.id,
6944
7525
  type: node.type,
6945
7526
  style: newStyle
6946
- }, { doNotPushHistory });
7527
+ }, {
7528
+ doNotPushHistory,
7529
+ historyDescription,
7530
+ historySource
7531
+ });
6947
7532
  }
6948
7533
  resetState() {
6949
7534
  this.set("root", null);
@@ -6983,18 +7568,83 @@
6983
7568
  if (this.selectionBeforeOp) return;
6984
7569
  this.selectionBeforeOp = this.get("nodes").map((n) => n.id);
6985
7570
  }
6986
- pushOpHistory(opType, extra, pageData, historyDescription) {
7571
+ /**
7572
+ * 比较「上一次 root」与「新 root」的页面 / 页面片,按页面粒度把整体替换拆成历史记录:
7573
+ * - 新旧都存在且内容变化的页面 → 一条 `update`(整页快照替换,无 changeRecords);
7574
+ * - 仅新 root 存在的页面 → 一条 `add`;
7575
+ * - 仅旧 root 存在的页面 → 一条 `remove`。
7576
+ *
7577
+ * 每条记录落到对应页面自己的历史栈(与普通节点操作一致),并标记来源 `source`。
7578
+ * 内容未变化的页面不产生记录,避免重复设置相同 DSL 时产生噪声。
7579
+ */
7580
+ pushRootDiffHistory(preRoot, nextRoot, source) {
7581
+ const prevPages = preRoot?.items || [];
7582
+ const nextPages = nextRoot?.items || [];
7583
+ const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
7584
+ const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
7585
+ const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
7586
+ nextPages.forEach((nextPage) => {
7587
+ const prevPage = prevMap.get(`${nextPage.id}`);
7588
+ if (!prevPage) this.pushPageDiffStep("add", nextPage, {
7589
+ newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
7590
+ parentId: nextRoot.id,
7591
+ index: indexInItems(nextRoot, nextPage.id)
7592
+ }, source);
7593
+ else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
7594
+ oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
7595
+ newSchema: cloneDeep$1((0, vue.toRaw)(nextPage))
7596
+ }, source);
7597
+ });
7598
+ prevPages.forEach((prevPage) => {
7599
+ if (!nextMap.has(`${prevPage.id}`)) this.pushPageDiffStep("remove", prevPage, {
7600
+ oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
7601
+ parentId: preRoot.id,
7602
+ index: indexInItems(preRoot, prevPage.id)
7603
+ }, source);
7604
+ });
7605
+ }
7606
+ /**
7607
+ * 构造一条页面级「set root」历史记录(不携带选区 / modifiedNodeIds 上下文)并落到该页面自己的栈。
7608
+ *
7609
+ * 连续 set root 替换:若该页栈最新一条已是**同来源**的 set root 记录({@link StepValue.rootStep} 且 `source` 相同),
7610
+ * 则用本次记录**替换**它而非新增,避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录;
7611
+ * 来源不同则照常新增(initial 基线不是 rootStep,不在此列)。
7612
+ */
7613
+ pushPageDiffStep(opType, page, diffItem, source) {
6987
7614
  const step = {
7615
+ uuid: (0, _tmagic_utils.guid)(),
7616
+ data: {
7617
+ name: page.name || "",
7618
+ id: page.id
7619
+ },
7620
+ opType,
7621
+ selectedBefore: [],
7622
+ selectedAfter: [],
7623
+ modifiedNodeIds: /* @__PURE__ */ new Map(),
7624
+ diff: [diffItem],
7625
+ rootStep: true
7626
+ };
7627
+ if (source) step.source = source;
7628
+ const top = history_default.getCurrentPageStep(page.id);
7629
+ if (top?.rootStep && top.source === source) history_default.replaceCurrentPageStep(step, page.id);
7630
+ else history_default.push(step, page.id);
7631
+ }
7632
+ pushOpHistory(opType, { diff, pageData, historyDescription, source }) {
7633
+ const step = {
7634
+ uuid: (0, _tmagic_utils.guid)(),
6988
7635
  data: pageData,
6989
7636
  opType,
6990
7637
  selectedBefore: this.selectionBeforeOp ?? [],
6991
7638
  selectedAfter: this.get("nodes").map((n) => n.id),
6992
7639
  modifiedNodeIds: new Map(this.get("modifiedNodeIds")),
6993
- ...extra
7640
+ diff
6994
7641
  };
6995
7642
  if (historyDescription) step.historyDescription = historyDescription;
6996
- history_default.push(step, pageData.id);
7643
+ if (source) step.source = source;
7644
+ const historyId = history_default.push(step, pageData.id) ? step.uuid : null;
7645
+ this.lastPushedHistoryId = historyId;
6997
7646
  this.selectionBeforeOp = null;
7647
+ return historyId;
6998
7648
  }
6999
7649
  /**
7000
7650
  * 应用历史操作(撤销 / 重做)
@@ -7009,6 +7659,7 @@
7009
7659
  * @param reverse true = 撤销,false = 重做
7010
7660
  */
7011
7661
  async applyHistoryOp(step, reverse) {
7662
+ if (step.opType === "initial") return;
7012
7663
  const root = this.get("root");
7013
7664
  const stage = this.get("stage");
7014
7665
  if (!root) return;
@@ -7019,56 +7670,59 @@
7019
7670
  };
7020
7671
  switch (step.opType) {
7021
7672
  case "add": {
7022
- const nodes = step.nodes ?? [];
7023
- if (reverse) for (const n of nodes) {
7024
- const existing = this.getNodeById(n.id, false);
7673
+ const items = step.diff ?? [];
7674
+ if (reverse) for (const { newSchema } of items) {
7675
+ if (!newSchema) continue;
7676
+ const existing = this.getNodeById(newSchema.id, false);
7025
7677
  if (existing) await this.remove(existing, commonOpts);
7026
7678
  }
7027
7679
  else {
7028
- const parent = this.getNodeById(step.parentId, false);
7029
- if (parent) {
7030
- const sorted = [...nodes].sort((a, b) => (step.indexMap?.[a.id] ?? 0) - (step.indexMap?.[b.id] ?? 0));
7031
- for (const n of sorted) {
7032
- const idx = step.indexMap?.[n.id];
7033
- if (parent.items) {
7034
- if (typeof idx === "number" && idx >= 0 && idx < parent.items.length) parent.items.splice(idx, 0, cloneDeep$1(n));
7035
- else parent.items.push(cloneDeep$1(n));
7036
- await stage?.add({
7037
- config: cloneDeep$1(n),
7038
- parent: cloneDeep$1(parent),
7039
- parentId: parent.id,
7040
- root: cloneDeep$1(root)
7041
- });
7042
- }
7680
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7681
+ for (const { newSchema, parentId, index } of sorted) {
7682
+ if (!newSchema || parentId === void 0) continue;
7683
+ const parent = this.getNodeById(parentId, false);
7684
+ if (parent?.items) {
7685
+ if (typeof index === "number" && index >= 0 && index < parent.items.length) parent.items.splice(index, 0, cloneDeep$1(newSchema));
7686
+ else parent.items.push(cloneDeep$1(newSchema));
7687
+ await stage?.add({
7688
+ config: cloneDeep$1(newSchema),
7689
+ parent: cloneDeep$1(parent),
7690
+ parentId: parent.id,
7691
+ root: cloneDeep$1(root)
7692
+ });
7043
7693
  }
7044
7694
  }
7045
7695
  }
7046
7696
  break;
7047
7697
  }
7048
7698
  case "remove": {
7049
- const items = step.removedItems ?? [];
7699
+ const items = step.diff ?? [];
7050
7700
  if (reverse) {
7051
- const sorted = [...items].sort((a, b) => a.index - b.index);
7052
- for (const { node, parentId, index } of sorted) {
7701
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7702
+ for (const { oldSchema, parentId, index } of sorted) {
7703
+ if (!oldSchema || parentId === void 0) continue;
7053
7704
  const parent = this.getNodeById(parentId, false);
7054
7705
  if (parent?.items) {
7055
- parent.items.splice(index, 0, cloneDeep$1(node));
7706
+ parent.items.splice(index ?? parent.items.length, 0, cloneDeep$1(oldSchema));
7056
7707
  await stage?.add({
7057
- config: cloneDeep$1(node),
7708
+ config: cloneDeep$1(oldSchema),
7058
7709
  parent: cloneDeep$1(parent),
7059
7710
  parentId,
7060
7711
  root: cloneDeep$1(root)
7061
7712
  });
7062
7713
  }
7063
7714
  }
7064
- } else for (const { node } of items) {
7065
- const existing = this.getNodeById(node.id, false);
7715
+ } else for (const { oldSchema } of items) {
7716
+ if (!oldSchema) continue;
7717
+ const existing = this.getNodeById(oldSchema.id, false);
7066
7718
  if (existing) await this.remove(existing, commonOpts);
7067
7719
  }
7068
7720
  break;
7069
7721
  }
7070
7722
  case "update": {
7071
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
7723
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
7724
+ const oldNode = oldSchema;
7725
+ const newNode = newSchema;
7072
7726
  if (changeRecords?.length) {
7073
7727
  const sourceForValues = reverse ? oldNode : newNode;
7074
7728
  const patch = {
@@ -7149,6 +7803,7 @@
7149
7803
  showPageListButton: true,
7150
7804
  hideSlideBar: false,
7151
7805
  sideBarItems: [],
7806
+ sideBarActiveTabName: "",
7152
7807
  navMenuRect: {
7153
7808
  left: 0,
7154
7809
  top: 0,
@@ -7181,7 +7836,9 @@
7181
7836
  }
7182
7837
  if (name === "showGuides") mask?.showGuides(value);
7183
7838
  if (name === "showRule") mask?.showRule(value);
7839
+ const preValue = state[name];
7184
7840
  state[name] = value;
7841
+ if (preValue !== value) this.emit("state-change", name, value, preValue);
7185
7842
  }
7186
7843
  get(name) {
7187
7844
  return state[name];
@@ -7319,14 +7976,17 @@
7319
7976
  changeRecordList.push(buildChangeRecords(style, "style"));
7320
7977
  });
7321
7978
  if (configs.length === 0) return;
7322
- editor_default.update(configs, { changeRecordList });
7979
+ editor_default.update(configs, {
7980
+ changeRecordList,
7981
+ historySource: "stage"
7982
+ });
7323
7983
  });
7324
7984
  stage.on("sort", (ev) => {
7325
- editor_default.sort(ev.src, ev.dist);
7985
+ editor_default.sort(ev.src, ev.dist, { historySource: "stage" });
7326
7986
  });
7327
7987
  stage.on("remove", (ev) => {
7328
7988
  const nodes = ev.data.map(({ el }) => editor_default.getNodeById((0, _tmagic_utils.getIdFromEl)()(el) || ""));
7329
- editor_default.remove(nodes.filter((node) => Boolean(node)));
7989
+ editor_default.remove(nodes.filter((node) => Boolean(node)), { historySource: "stage" });
7330
7990
  });
7331
7991
  stage.on("select-parent", () => {
7332
7992
  const parent = editor_default.get("parent");
@@ -8401,7 +9061,7 @@
8401
9061
  key: 1,
8402
9062
  class: "menu-item-text"
8403
9063
  };
8404
- var _hoisted_2$28 = { class: "el-dropdown-link menubar-menu-button" };
9064
+ var _hoisted_2$30 = { class: "el-dropdown-link menubar-menu-button" };
8405
9065
  var ToolButton_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8406
9066
  name: "MEditorToolButton",
8407
9067
  __name: "ToolButton",
@@ -8466,11 +9126,11 @@
8466
9126
  placement: "bottom-start",
8467
9127
  content: __props.data.tooltip
8468
9128
  }, {
8469
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
9129
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8470
9130
  size: "small",
8471
9131
  link: "",
8472
9132
  disabled: disabled.value
8473
- }, (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
9133
+ }, __props.data.buttonProps || {}), (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
8474
9134
  name: "icon",
8475
9135
  fn: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: __props.data.icon }, null, 8, ["icon"])]),
8476
9136
  key: "0"
@@ -8478,15 +9138,15 @@
8478
9138
  name: "default",
8479
9139
  fn: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1)]),
8480
9140
  key: "1"
8481
- } : void 0]), 1032, ["disabled"])]),
9141
+ } : void 0]), 1040, ["disabled"])]),
8482
9142
  _: 1
8483
- }, 8, ["content"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
9143
+ }, 8, ["content"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8484
9144
  key: 1,
8485
9145
  size: "small",
8486
9146
  link: "",
8487
9147
  disabled: disabled.value,
8488
9148
  title: __props.data.text
8489
- }, (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
9149
+ }, __props.data.buttonProps || {}), (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
8490
9150
  name: "icon",
8491
9151
  fn: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: __props.data.icon }, null, 8, ["icon"])]),
8492
9152
  key: "0"
@@ -8494,7 +9154,7 @@
8494
9154
  name: "default",
8495
9155
  fn: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1)]),
8496
9156
  key: "1"
8497
- } : void 0]), 1032, ["disabled", "title"]))], 64)) : __props.data.type === "dropdown" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDropdown), {
9157
+ } : void 0]), 1040, ["disabled", "title"]))], 64)) : __props.data.type === "dropdown" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDropdown), {
8498
9158
  key: 3,
8499
9159
  trigger: "click",
8500
9160
  disabled: disabled.value,
@@ -8515,7 +9175,7 @@
8515
9175
  }), 128))]),
8516
9176
  _: 1
8517
9177
  })) : (0, vue.createCommentVNode)("v-if", true)]),
8518
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("span", _hoisted_2$28, [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicIcon), { class: "el-icon--right" }, {
9178
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("span", _hoisted_2$30, [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicIcon), { class: "el-icon--right" }, {
8519
9179
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_element_plus_icons_vue.ArrowDown))]),
8520
9180
  _: 1
8521
9181
  })])]),
@@ -8534,7 +9194,7 @@
8534
9194
  id: "m-editor-page-bar-add-icon",
8535
9195
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon"
8536
9196
  };
8537
- var _hoisted_2$27 = {
9197
+ var _hoisted_2$29 = {
8538
9198
  key: 1,
8539
9199
  style: { "width": "21px" }
8540
9200
  };
@@ -8571,7 +9231,7 @@
8571
9231
  }
8572
9232
  } }, null, 8, ["data"])]),
8573
9233
  _: 1
8574
- })])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$27));
9234
+ })])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$29));
8575
9235
  };
8576
9236
  }
8577
9237
  });
@@ -8584,7 +9244,7 @@
8584
9244
  class: "m-editor-page-bar",
8585
9245
  ref: "pageBar"
8586
9246
  };
8587
- var _hoisted_2$26 = {
9247
+ var _hoisted_2$28 = {
8588
9248
  key: 0,
8589
9249
  class: "m-editor-page-bar-items",
8590
9250
  ref: "itemsContainer"
@@ -8676,7 +9336,7 @@
8676
9336
  return (_ctx, _cache) => {
8677
9337
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$68, [
8678
9338
  (0, vue.renderSlot)(_ctx.$slots, "prepend"),
8679
- __props.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$26, [(0, vue.renderSlot)(_ctx.$slots, "default")], 512)) : (0, vue.createCommentVNode)("v-if", true),
9339
+ __props.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$28, [(0, vue.renderSlot)(_ctx.$slots, "default")], 512)) : (0, vue.createCommentVNode)("v-if", true),
8680
9340
  canScroll.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
8681
9341
  key: 1,
8682
9342
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon m-editor-page-bar-item-left-icon",
@@ -8701,8 +9361,8 @@
8701
9361
  id: "m-editor-page-bar-list-icon",
8702
9362
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon"
8703
9363
  };
8704
- var _hoisted_2$25 = { class: "page-bar-popover-wrapper" };
8705
- var _hoisted_3$10 = { class: "page-bar-popover-inner" };
9364
+ var _hoisted_2$27 = { class: "page-bar-popover-wrapper" };
9365
+ var _hoisted_3$13 = { class: "page-bar-popover-inner" };
8706
9366
  var PageList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8707
9367
  name: "MEditorPageList",
8708
9368
  __name: "PageList",
@@ -8726,7 +9386,7 @@
8726
9386
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_element_plus_icons_vue.Files))]),
8727
9387
  _: 1
8728
9388
  })]),
8729
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_2$25, [(0, vue.createElementVNode)("div", _hoisted_3$10, [(0, vue.renderSlot)(_ctx.$slots, "page-list-popover", { list: __props.list }, () => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.list, (item, index) => {
9389
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_2$27, [(0, vue.createElementVNode)("div", _hoisted_3$13, [(0, vue.renderSlot)(_ctx.$slots, "page-list-popover", { list: __props.list }, () => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.list, (item, index) => {
8730
9390
  return (0, vue.openBlock)(), (0, vue.createBlock)(ToolButton_default, {
8731
9391
  data: {
8732
9392
  type: "button",
@@ -8805,9 +9465,9 @@
8805
9465
  //#endregion
8806
9466
  //#region packages/editor/src/layouts/page-bar/PageBar.vue?vue&type=script&setup=true&lang.ts
8807
9467
  var _hoisted_1$65 = { class: "m-editor-page-bar-tabs" };
8808
- var _hoisted_2$24 = ["data-page-id", "onClick"];
8809
- var _hoisted_3$9 = { class: "m-editor-page-bar-title" };
8810
- var _hoisted_4$8 = ["title"];
9468
+ var _hoisted_2$26 = ["data-page-id", "onClick"];
9469
+ var _hoisted_3$12 = { class: "m-editor-page-bar-title" };
9470
+ var _hoisted_4$9 = ["title"];
8811
9471
  var PageBar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8812
9472
  name: "MEditorPageBar",
8813
9473
  __name: "PageBar",
@@ -8849,7 +9509,8 @@
8849
9509
  top: 0
8850
9510
  });
8851
9511
  };
8852
- const remove = (node) => {
9512
+ const remove = async (node) => {
9513
+ await _tmagic_design.tMagicMessageBox.confirm("确定删除该页面吗?");
8853
9514
  editorService.remove(node);
8854
9515
  };
8855
9516
  const pageBarScrollContainerRef = (0, vue.useTemplateRef)("pageBarScrollContainer");
@@ -8896,7 +9557,7 @@
8896
9557
  key: item.id,
8897
9558
  "data-page-id": item.id,
8898
9559
  onClick: ($event) => switchPage(item.id)
8899
- }, [(0, vue.createElementVNode)("div", _hoisted_3$9, [(0, vue.renderSlot)(_ctx.$slots, "page-bar-title", { page: item }, () => [(0, vue.createElementVNode)("span", { title: item.name }, (0, vue.toDisplayString)(item.name || item.id), 9, _hoisted_4$8)])]), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
9560
+ }, [(0, vue.createElementVNode)("div", _hoisted_3$12, [(0, vue.renderSlot)(_ctx.$slots, "page-bar-title", { page: item }, () => [(0, vue.createElementVNode)("span", { title: item.name }, (0, vue.toDisplayString)(item.name || item.id), 9, _hoisted_4$9)])]), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
8900
9561
  "popper-class": "page-bar-popover",
8901
9562
  placement: "top",
8902
9563
  trigger: "hover",
@@ -8916,10 +9577,11 @@
8916
9577
  type: "button",
8917
9578
  text: "删除",
8918
9579
  icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
9580
+ buttonProps: { type: "danger" },
8919
9581
  handler: () => remove(item)
8920
9582
  } }, null, 8, ["data"])])])]),
8921
9583
  _: 2
8922
- }, 1024)], 10, _hoisted_2$24);
9584
+ }, 1024)], 10, _hoisted_2$26);
8923
9585
  }), 128))]),
8924
9586
  _: 3
8925
9587
  }, 8, ["page-bar-sort-options", "length"])]);
@@ -8932,7 +9594,7 @@
8932
9594
  //#endregion
8933
9595
  //#region packages/editor/src/layouts/AddPageBox.vue?vue&type=script&setup=true&lang.ts
8934
9596
  var _hoisted_1$64 = { class: "m-editor-empty-panel" };
8935
- var _hoisted_2$23 = { class: "m-editor-empty-content" };
9597
+ var _hoisted_2$25 = { class: "m-editor-empty-content" };
8936
9598
  var AddPageBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8937
9599
  name: "MEditorAddPageBox",
8938
9600
  __name: "AddPageBox",
@@ -8949,7 +9611,7 @@
8949
9611
  });
8950
9612
  };
8951
9613
  return (_ctx, _cache) => {
8952
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$64, [(0, vue.createElementVNode)("div", _hoisted_2$23, [(0, vue.createElementVNode)("div", {
9614
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$64, [(0, vue.createElementVNode)("div", _hoisted_2$25, [(0, vue.createElementVNode)("div", {
8953
9615
  class: "m-editor-empty-button",
8954
9616
  onClick: _cache[0] || (_cache[0] = ($event) => clickHandler((0, vue.unref)(_tmagic_core.NodeType).PAGE))
8955
9617
  }, [(0, vue.createElementVNode)("div", null, [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(_element_plus_icons_vue.Plus) }, null, 8, ["icon"])]), _cache[2] || (_cache[2] = (0, vue.createElementVNode)("p", null, "新增页面", -1))]), !__props.disabledPageFragment ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
@@ -8966,7 +9628,7 @@
8966
9628
  //#endregion
8967
9629
  //#region packages/editor/src/layouts/CodeEditor.vue?vue&type=script&setup=true&lang.ts
8968
9630
  var _hoisted_1$63 = { class: "magic-code-editor" };
8969
- var _hoisted_2$22 = {
9631
+ var _hoisted_2$24 = {
8970
9632
  ref: "codeEditor",
8971
9633
  class: "magic-code-editor-content"
8972
9634
  };
@@ -9221,7 +9883,7 @@
9221
9883
  }, {
9222
9884
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(_element_plus_icons_vue.FullScreen) }, null, 8, ["icon"])]),
9223
9885
  _: 1
9224
- })) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createElementVNode)("div", _hoisted_2$22, null, 512)], 6)], 8, ["disabled"]))]);
9886
+ })) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createElementVNode)("div", _hoisted_2$24, null, 512)], 6)], 8, ["disabled"]))]);
9225
9887
  };
9226
9888
  }
9227
9889
  });
@@ -9288,7 +9950,7 @@
9288
9950
  const saveCode = (value) => {
9289
9951
  try {
9290
9952
  const parseDSL = getEditorConfig("parseDSL");
9291
- editorService.set("root", parseDSL(value));
9953
+ editorService.set("root", parseDSL(value), { historySource: "root-code" });
9292
9954
  } catch (e) {
9293
9955
  console.error(e);
9294
9956
  }
@@ -9362,6 +10024,8 @@
9362
10024
  var Framework_default = Framework_vue_vue_type_script_setup_true_lang_default;
9363
10025
  //#endregion
9364
10026
  //#region packages/editor/src/layouts/history-list/composables.ts
10027
+ /** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
10028
+ var isHistoryGroupExpanded = (expanded, key) => expanded[key] !== false;
9365
10029
  /**
9366
10030
  * 历史记录面板共享逻辑:
9367
10031
  * - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
@@ -9375,10 +10039,11 @@
9375
10039
  /**
9376
10040
  * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
9377
10041
  * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
10042
+ * 合并组默认展开;仅当值为 `false` 时表示收起。
9378
10043
  */
9379
10044
  const expanded = (0, vue.reactive)({});
9380
10045
  const toggleGroup = (key) => {
9381
- expanded[key] = !expanded[key];
10046
+ expanded[key] = expanded[key] === false;
9382
10047
  };
9383
10048
  const pageGroups = (0, vue.computed)(() => historyService.getPageHistoryGroups());
9384
10049
  const dataSourceGroups = (0, vue.computed)(() => historyService.getDataSourceHistoryGroups());
@@ -9433,6 +10098,62 @@
9433
10098
  default: return "修改";
9434
10099
  }
9435
10100
  };
10101
+ /** 内置操作途径的中文文案;自定义来源直接回显原值,未知 / 缺省返回空串(UI 据此不渲染)。 */
10102
+ var HISTORY_SOURCE_LABELS = {
10103
+ stage: "画布",
10104
+ tree: "树面板",
10105
+ "component-panel": "组件面板",
10106
+ props: "配置面板",
10107
+ code: "源码",
10108
+ "root-code": "DSL源码",
10109
+ "stage-contextmenu": "画布菜单",
10110
+ "tree-contextmenu": "树菜单",
10111
+ toolbar: "工具栏",
10112
+ shortcut: "快捷键",
10113
+ rollback: "回滚",
10114
+ api: "接口",
10115
+ ai: "AI",
10116
+ initial: "初始值",
10117
+ sync: "同步",
10118
+ unknown: "未知"
10119
+ };
10120
+ /** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
10121
+ var sourceLabel = (source = "unknown") => {
10122
+ return HISTORY_SOURCE_LABELS[source] ?? `${source}`;
10123
+ };
10124
+ /** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
10125
+ var groupSource = (group) => group.steps[group.steps.length - 1]?.step.source;
10126
+ /**
10127
+ * 把一个历史分组(页面 / bucket)派生为 GroupRow 直接消费的视图模型 {@link HistoryRowGroup}。
10128
+ * 统一了原先 PageTab / Bucket 各自内联的 sub-steps 映射逻辑:描述、可差异、可回滚、时间、途径
10129
+ * 全部在此一次性算好,组件层只负责渲染。
10130
+ */
10131
+ var toRowGroup = (group, key, descriptor) => {
10132
+ const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
10133
+ const timestamp = groupTimestamp(group);
10134
+ return {
10135
+ key,
10136
+ applied: group.applied,
10137
+ isCurrent: Boolean(group.isCurrent),
10138
+ opType: group.opType,
10139
+ desc: describeGroup(group),
10140
+ source: groupSource(group),
10141
+ time: formatHistoryTime(timestamp),
10142
+ timeTitle: formatHistoryFullTime(timestamp),
10143
+ subSteps: group.steps.map((s) => ({
10144
+ index: s.index,
10145
+ applied: s.applied,
10146
+ isCurrent: s.isCurrent,
10147
+ saved: s.step.saved,
10148
+ desc: describeStep(s.step),
10149
+ diffable: isStepDiffable ? isStepDiffable(s.step) : false,
10150
+ revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
10151
+ source: s.step.source,
10152
+ time: formatHistoryTime(s.step.timestamp),
10153
+ timeTitle: formatHistoryFullTime(s.step.timestamp)
10154
+ }))
10155
+ };
10156
+ };
9436
10157
  var nameOf = (node) => node?.name || node?.type || `${node?.id ?? ""}`;
9437
10158
  /**
9438
10159
  * 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
@@ -9451,24 +10172,24 @@
9451
10172
  var describePageStep = (step) => {
9452
10173
  if (step.historyDescription) return step.historyDescription;
9453
10174
  const { opType } = step;
10175
+ const items = step.diff ?? [];
9454
10176
  if (opType === "add") {
9455
- const count = step.nodes?.length ?? 0;
9456
- const node = step.nodes?.[0];
10177
+ const count = items.length;
10178
+ const node = items[0]?.newSchema;
9457
10179
  return `新增 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
9458
10180
  }
9459
10181
  if (opType === "remove") {
9460
- const count = step.removedItems?.length ?? 0;
9461
- const node = step.removedItems?.[0]?.node;
10182
+ const count = items.length;
10183
+ const node = items[0]?.oldSchema;
9462
10184
  return `删除 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
9463
10185
  }
9464
- const updated = step.updatedItems ?? [];
9465
- if (!updated.length) return "修改节点";
9466
- if (updated.length === 1) {
9467
- const { newNode, changeRecords } = updated[0];
10186
+ if (!items.length) return "修改节点";
10187
+ if (items.length === 1) {
10188
+ const { newSchema, changeRecords } = items[0];
9468
10189
  const propPath = changeRecords?.[0]?.propPath;
9469
- return `修改 ${labelWithId(nameOf(newNode), newNode?.id)}${propPath ? ` · ${propPath}` : ""}`;
10190
+ return `修改 ${labelWithId(nameOf(newSchema), newSchema?.id)}${propPath ? ` · ${propPath}` : ""}`;
9470
10191
  }
9471
- return `修改 ${updated.length} 个节点`;
10192
+ return `修改 ${items.length} 个节点`;
9472
10193
  };
9473
10194
  /**
9474
10195
  * 合并组的展示文案:
@@ -9482,7 +10203,7 @@
9482
10203
  if (group.steps.length === 1) return describePageStep(group.steps[0].step);
9483
10204
  const paths = /* @__PURE__ */ new Set();
9484
10205
  group.steps.forEach((s) => {
9485
- s.step.updatedItems?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10206
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9486
10207
  });
9487
10208
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9488
10209
  const target = labelWithId(group.targetName ?? (group.targetId !== void 0 ? `${group.targetId}` : "节点"), group.targetId);
@@ -9490,10 +10211,11 @@
9490
10211
  };
9491
10212
  var describeDataSourceStep = (step) => {
9492
10213
  if (step.historyDescription) return step.historyDescription;
9493
- if (step.oldSchema === null && step.newSchema) return `创建 ${labelWithId(step.newSchema.title, step.newSchema.id ?? step.id)}`;
9494
- if (step.newSchema === null && step.oldSchema) return `删除 ${labelWithId(step.oldSchema.title, step.oldSchema.id ?? step.id)}`;
9495
- const propPath = step.changeRecords?.[0]?.propPath;
9496
- const title = labelWithId(step.newSchema?.title || step.oldSchema?.title, step.id);
10214
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
10215
+ if (!oldSchema && newSchema) return `创建 ${labelWithId(newSchema.title, newSchema.id ?? step.id)}`;
10216
+ if (!newSchema && oldSchema) return `删除 ${labelWithId(oldSchema.title, oldSchema.id ?? step.id)}`;
10217
+ const propPath = changeRecords?.[0]?.propPath;
10218
+ const title = labelWithId(newSchema?.title || oldSchema?.title, step.id);
9497
10219
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
9498
10220
  };
9499
10221
  var describeDataSourceGroup = (group) => {
@@ -9502,18 +10224,19 @@
9502
10224
  if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
9503
10225
  const paths = /* @__PURE__ */ new Set();
9504
10226
  group.steps.forEach((s) => {
9505
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10227
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9506
10228
  });
9507
10229
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9508
- const target = labelWithId(group.steps[group.steps.length - 1].step.newSchema?.title || group.steps[0].step.oldSchema?.title, group.id);
10230
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.title || group.steps[0].step.diff?.[0]?.oldSchema?.title, group.id);
9509
10231
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
9510
10232
  };
9511
10233
  var describeCodeBlockStep = (step) => {
9512
10234
  if (step.historyDescription) return step.historyDescription;
9513
- if (step.oldContent === null && step.newContent) return `创建 ${labelWithId(step.newContent.name, step.newContent.id ?? step.id)}`;
9514
- if (step.newContent === null && step.oldContent) return `删除 ${labelWithId(step.oldContent.name, step.oldContent.id ?? step.id)}`;
9515
- const propPath = step.changeRecords?.[0]?.propPath;
9516
- const title = labelWithId(step.newContent?.name || step.oldContent?.name, step.id);
10235
+ const { oldSchema: oldContent, newSchema: newContent, changeRecords } = step.diff?.[0] ?? {};
10236
+ if (!oldContent && newContent) return `创建 ${labelWithId(newContent.name, newContent.id ?? step.id)}`;
10237
+ if (!newContent && oldContent) return `删除 ${labelWithId(oldContent.name, oldContent.id ?? step.id)}`;
10238
+ const propPath = changeRecords?.[0]?.propPath;
10239
+ const title = labelWithId(newContent?.name || oldContent?.name, step.id);
9517
10240
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
9518
10241
  };
9519
10242
  var describeCodeBlockGroup = (group) => {
@@ -9522,117 +10245,190 @@
9522
10245
  if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
9523
10246
  const paths = /* @__PURE__ */ new Set();
9524
10247
  group.steps.forEach((s) => {
9525
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10248
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9526
10249
  });
9527
10250
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9528
- const target = labelWithId(group.steps[group.steps.length - 1].step.newContent?.name || group.steps[0].step.oldContent?.name, group.id);
10251
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.name || group.steps[0].step.diff?.[0]?.oldSchema?.name, group.id);
9529
10252
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
9530
10253
  };
10254
+ /**
10255
+ * 页面 step 是否支持「回滚」(类 git revert):
10256
+ * - 新增 / 删除:不依赖 changeRecords,反向应用即删除 / 加回,始终可回滚;
10257
+ * - 更新:必须每个被更新节点都带有 changeRecords,才支持按 propPath 局部反向 patch。
10258
+ * 缺失 changeRecords 的更新只能整节点替换,会冲掉该节点后续的无关变更,因此不支持回滚。
10259
+ */
10260
+ var isPageStepRevertable = (step) => {
10261
+ if (step.opType !== "update") return true;
10262
+ const items = step.diff ?? [];
10263
+ if (!items.length) return false;
10264
+ return items.every((item) => Boolean(item.changeRecords?.length));
10265
+ };
10266
+ /**
10267
+ * 数据源 step 是否支持「回滚」:
10268
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
10269
+ * - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
10270
+ */
10271
+ var isDataSourceStepRevertable = (step) => {
10272
+ const item = step.diff?.[0];
10273
+ if (!item?.oldSchema || !item?.newSchema) return true;
10274
+ return Boolean(item.changeRecords?.length);
10275
+ };
10276
+ /**
10277
+ * 代码块 step 是否支持「回滚」:
10278
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
10279
+ * - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
10280
+ */
10281
+ var isCodeBlockStepRevertable = (step) => {
10282
+ const item = step.diff?.[0];
10283
+ if (!item?.oldSchema || !item?.newSchema) return true;
10284
+ return Boolean(item.changeRecords?.length);
10285
+ };
9531
10286
  //#endregion
9532
10287
  //#region packages/editor/src/layouts/history-list/GroupRow.vue?vue&type=script&setup=true&lang.ts
9533
10288
  var _hoisted_1$61 = ["title"];
9534
- var _hoisted_2$21 = ["title"];
9535
- var _hoisted_3$8 = { class: "m-editor-history-list-item-desc" };
9536
- var _hoisted_4$7 = ["title"];
10289
+ var _hoisted_2$23 = ["title"];
10290
+ var _hoisted_3$11 = { class: "m-editor-history-list-item-desc" };
10291
+ var _hoisted_4$8 = {
10292
+ key: 0,
10293
+ class: "m-editor-history-list-item-saved",
10294
+ title: "该记录为最近一次保存的状态"
10295
+ };
9537
10296
  var _hoisted_5$3 = {
9538
10297
  key: 1,
10298
+ class: "m-editor-history-list-item-actions"
10299
+ };
10300
+ var _hoisted_6$3 = ["title"];
10301
+ var _hoisted_7$1 = ["title"];
10302
+ var _hoisted_8 = {
10303
+ key: 4,
9539
10304
  class: "m-editor-history-list-item-merge"
9540
10305
  };
9541
- var _hoisted_6$3 = {
10306
+ var _hoisted_9 = {
9542
10307
  key: 0,
9543
10308
  class: "m-editor-history-list-substeps"
9544
10309
  };
9545
- var _hoisted_7$1 = ["title"];
9546
- var _hoisted_8 = { class: "m-editor-history-list-item-index" };
9547
- var _hoisted_9 = { class: "m-editor-history-list-substep-desc" };
9548
- var _hoisted_10 = ["title"];
9549
- var _hoisted_11 = ["onClick"];
9550
- var _hoisted_12 = ["onClick"];
9551
- var _hoisted_13 = ["onClick"];
10310
+ var _hoisted_10 = ["title", "onClick"];
10311
+ var _hoisted_11 = { class: "m-editor-history-list-item-index" };
10312
+ var _hoisted_12 = { class: "m-editor-history-list-substep-desc" };
10313
+ var _hoisted_13 = {
10314
+ key: 0,
10315
+ class: "m-editor-history-list-item-saved",
10316
+ title: "该记录为最近一次保存的状态"
10317
+ };
10318
+ var _hoisted_14 = {
10319
+ key: 1,
10320
+ class: "m-editor-history-list-item-actions"
10321
+ };
10322
+ var _hoisted_15 = ["onClick"];
10323
+ var _hoisted_16 = ["onClick"];
10324
+ var _hoisted_17 = ["onClick"];
10325
+ var _hoisted_18 = ["title"];
10326
+ var _hoisted_19 = ["title"];
9552
10327
  var GroupRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9553
10328
  name: "MEditorHistoryListGroupRow",
9554
10329
  __name: "GroupRow",
9555
10330
  props: {
9556
- groupKey: {},
9557
- applied: { type: Boolean },
9558
- merged: { type: Boolean },
9559
- opType: {},
9560
- desc: {},
9561
- time: {},
9562
- timeTitle: {},
9563
- stepCount: {},
9564
- subSteps: {},
10331
+ group: {},
9565
10332
  expanded: { type: Boolean },
9566
- isCurrent: {
9567
- type: Boolean,
9568
- default: false
9569
- },
9570
10333
  gotoEnabled: {
9571
10334
  type: Boolean,
9572
10335
  default: true
10336
+ },
10337
+ selectEnabled: {
10338
+ type: Boolean,
10339
+ default: false
9573
10340
  }
9574
10341
  },
9575
10342
  emits: [
9576
10343
  "toggle",
9577
10344
  "goto",
9578
10345
  "diff-step",
9579
- "revert-step"
10346
+ "revert-step",
10347
+ "select"
9580
10348
  ],
9581
10349
  setup(__props, { emit: __emit }) {
9582
10350
  const props = __props;
9583
10351
  const emit = __emit;
10352
+ /** 子步数大于 1 即为合并组:决定是否展示合并标记与可展开的子步列表。 */
10353
+ const merged = (0, vue.computed)(() => props.group.subSteps.length > 1);
10354
+ /** 组内 step 总数,仅在合并组时显示为 "合并 N 步"。 */
10355
+ const stepCount = (0, vue.computed)(() => props.group.subSteps.length);
9584
10356
  /**
9585
- * 仅合并组头部可点击(切换展开 / 收起);
9586
- * 单步组的跳转改由头部的「回退」按钮触发,整行不再可点击。
10357
+ * 头部可点击的场景:
10358
+ * - 合并组:点击切换展开 / 收起;
10359
+ * - 开启 `selectEnabled`(页面 tab):点击选中对应节点。
10360
+ * 单步组的跳转仍由头部的「回到」按钮触发。
9587
10361
  */
9588
- const isHeadClickable = (0, vue.computed)(() => props.merged);
10362
+ const isHeadClickable = (0, vue.computed)(() => merged.value || props.selectEnabled);
9589
10363
  const headTitle = (0, vue.computed)(() => {
9590
- if (props.merged) return props.expanded ? "点击收起子步" : "点击展开子步";
9591
- if (props.isCurrent) return "当前所在记录";
10364
+ if (merged.value) {
10365
+ const expandHint = props.expanded ? "点击收起子步" : "点击展开子步";
10366
+ return props.selectEnabled ? `${expandHint}(并选中该节点)` : expandHint;
10367
+ }
10368
+ if (props.selectEnabled) return "点击选中该节点";
10369
+ if (props.group.isCurrent) return "当前所在记录";
9592
10370
  return "";
9593
10371
  });
9594
10372
  /**
9595
- * 头部点击行为:仅合并组切换展开 / 收起;单步组不再响应整行点击。
10373
+ * 头部点击行为:
10374
+ * - 开启 selectEnabled 时,发出 select(携带组内首步 index,上层据此选中节点);
10375
+ * - 合并组同时切换展开 / 收起。
9596
10376
  */
9597
10377
  const onHeadClick = () => {
9598
- if (props.merged) emit("toggle", props.groupKey);
10378
+ if (props.selectEnabled && props.group.subSteps.length) emit("select", props.group.subSteps[0].index);
10379
+ if (merged.value) emit("toggle", props.group.key);
9599
10380
  };
9600
10381
  const onGotoClick = (index) => {
9601
10382
  if (!props.gotoEnabled) return;
9602
10383
  emit("goto", index);
9603
10384
  };
10385
+ /** 点击子步行:开启 selectEnabled 时选中该子步对应的节点。 */
10386
+ const onSubStepClick = (index) => {
10387
+ if (!props.selectEnabled) return;
10388
+ emit("select", index);
10389
+ };
9604
10390
  const subStepTitle = (s) => {
10391
+ if (props.selectEnabled) return "点击选中该节点";
9605
10392
  if (s.isCurrent) return "当前所在记录";
9606
10393
  return "";
9607
10394
  };
10395
+ /**
10396
+ * 头部是否展示「已保存」标记:
10397
+ * - 单步组:取该唯一子步的 saved;
10398
+ * - 合并组:组内任一子步为已保存即在头部提示(具体落在哪一步可展开查看)。
10399
+ */
10400
+ const headSaved = (0, vue.computed)(() => merged.value ? props.group.subSteps.some((s) => s.saved) : Boolean(props.group.subSteps[0]?.saved));
9608
10401
  /** 单步组头部是否展示"查看差异"入口:要求该唯一子步本身可对比。 */
9609
- const headDiffable = (0, vue.computed)(() => !props.merged && Boolean(props.subSteps[0]?.diffable));
10402
+ const headDiffable = (0, vue.computed)(() => !merged.value && Boolean(props.group.subSteps[0]?.diffable));
9610
10403
  /** 单步组头部是否展示"回滚"入口:要求该唯一子步本身可回滚(已应用)。 */
9611
- const headRevertable = (0, vue.computed)(() => !props.merged && Boolean(props.subSteps[0]?.revertable));
10404
+ const headRevertable = (0, vue.computed)(() => !merged.value && Boolean(props.group.subSteps[0]?.revertable));
10405
+ /** 单步组头部是否展示"回到"入口:可跳转、非当前、且存在唯一子步。 */
10406
+ const canHeadGoto = (0, vue.computed)(() => !merged.value && props.gotoEnabled && !props.group.isCurrent && props.group.subSteps.length > 0);
9612
10407
  /**
9613
10408
  * 合并组展开后的子步渲染顺序:与外层分组列表保持一致——倒序展示(最新的子步在最上方)。
9614
10409
  * 外层 page tab / bucket 都已对 groups 做了 reverse,子步沿用同样的视觉规则更直观。
9615
10410
  * 注意:仅用于渲染,原 `subSteps` 保持时间正序,`headIndexLabel` 等基于首尾索引的展示语义不变。
9616
10411
  */
9617
- const subStepsDisplay = (0, vue.computed)(() => props.subSteps.slice().reverse());
10412
+ const subStepsDisplay = (0, vue.computed)(() => props.group.subSteps.slice().reverse());
9618
10413
  /**
9619
10414
  * 头部索引展示:
9620
- * - 单步组(merged=false):显示该唯一 step 的编号,如 `#5`;
10415
+ * - 单步组(非合并):显示该唯一 step 的编号,如 `#5`;
9621
10416
  * - 合并组:显示组内 step 的编号范围,如 `#3-#7`(首尾相同则退化为 `#5`)。
9622
10417
  *
9623
10418
  * 这里展示的是 step.index + 1(与子步列表 `#{{ s.index + 1 }}` 保持一致),从 1 起编号更符合直觉。
9624
10419
  */
9625
10420
  const headIndexLabel = (0, vue.computed)(() => {
9626
- const list = props.subSteps;
10421
+ const list = props.group.subSteps;
9627
10422
  if (!list.length) return "";
9628
10423
  const first = list[0].index + 1;
9629
10424
  const last = list[list.length - 1].index + 1;
9630
- if (!props.merged || first === last) return `#${first}`;
10425
+ if (!merged.value || first === last) return `#${first}`;
9631
10426
  return `#${first}-#${last}`;
9632
10427
  });
9633
10428
  const headIndexTitle = (0, vue.computed)(() => {
9634
- if (!props.merged) return `历史步骤编号 #${props.subSteps[0]?.index + 1}`;
9635
- return `合并了第 ${props.subSteps[0]?.index + 1} 至第 ${props.subSteps[props.subSteps.length - 1]?.index + 1} 共 ${props.subSteps.length} 条历史步骤`;
10429
+ const list = props.group.subSteps;
10430
+ if (!merged.value) return `历史步骤编号 #${list[0]?.index + 1}`;
10431
+ return `合并了第 ${list[0]?.index + 1} 至第 ${list[list.length - 1]?.index + 1} 共 ${list.length} 条历史步骤`;
9636
10432
  });
9637
10433
  const onDiffClick = (index) => {
9638
10434
  emit("diff-step", index);
@@ -9642,9 +10438,9 @@
9642
10438
  };
9643
10439
  return (_ctx, _cache) => {
9644
10440
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("li", { class: (0, vue.normalizeClass)(["m-editor-history-list-item m-editor-history-list-group", {
9645
- "is-undone": !__props.applied,
9646
- "is-merged": __props.merged,
9647
- "is-current": __props.isCurrent
10441
+ "is-undone": !__props.group.applied,
10442
+ "is-merged": merged.value,
10443
+ "is-current": __props.group.isCurrent
9648
10444
  }]) }, [(0, vue.createElementVNode)("div", {
9649
10445
  class: (0, vue.normalizeClass)(["m-editor-history-list-group-head", { "is-clickable": isHeadClickable.value }]),
9650
10446
  title: headTitle.value,
@@ -9653,72 +10449,90 @@
9653
10449
  (0, vue.createElementVNode)("span", {
9654
10450
  class: "m-editor-history-list-item-index",
9655
10451
  title: headIndexTitle.value
9656
- }, (0, vue.toDisplayString)(headIndexLabel.value), 9, _hoisted_2$21),
9657
- (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["m-editor-history-list-item-op", `op-${__props.opType}`]) }, (0, vue.toDisplayString)((0, vue.unref)(opLabel)(__props.opType)), 3),
9658
- (0, vue.createElementVNode)("span", _hoisted_3$8, (0, vue.toDisplayString)(__props.desc), 1),
9659
- __props.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9660
- key: 0,
9661
- class: "m-editor-history-list-item-time",
9662
- title: __props.timeTitle || __props.time
9663
- }, (0, vue.toDisplayString)(__props.time), 9, _hoisted_4$7)) : (0, vue.createCommentVNode)("v-if", true),
9664
- __props.merged ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$3, "合并 " + (0, vue.toDisplayString)(__props.stepCount) + " 步", 1)) : (0, vue.createCommentVNode)("v-if", true),
9665
- !__props.merged && headRevertable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10452
+ }, (0, vue.toDisplayString)(headIndexLabel.value), 9, _hoisted_2$23),
10453
+ (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["m-editor-history-list-item-op", `op-${__props.group.opType}`]) }, (0, vue.toDisplayString)((0, vue.unref)(opLabel)(__props.group.opType)), 3),
10454
+ (0, vue.createElementVNode)("span", _hoisted_3$11, (0, vue.toDisplayString)(__props.group.desc), 1),
10455
+ headSaved.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$8, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10456
+ !merged.value && (headRevertable.value || headDiffable.value || canHeadGoto.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$3, [
10457
+ headRevertable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10458
+ key: 0,
10459
+ class: "m-editor-history-list-item-revert",
10460
+ title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
10461
+ onClick: _cache[0] || (_cache[0] = (0, vue.withModifiers)(($event) => onRevertClick(__props.group.subSteps[0].index), ["stop"]))
10462
+ }, "回滚")) : (0, vue.createCommentVNode)("v-if", true),
10463
+ canHeadGoto.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10464
+ key: 1,
10465
+ class: "m-editor-history-list-item-goto",
10466
+ title: "回到该记录",
10467
+ onClick: _cache[1] || (_cache[1] = (0, vue.withModifiers)(($event) => onGotoClick(__props.group.subSteps[0].index), ["stop"]))
10468
+ }, "回到")) : (0, vue.createCommentVNode)("v-if", true),
10469
+ headDiffable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10470
+ key: 2,
10471
+ class: "m-editor-history-list-item-diff",
10472
+ title: "查看修改差异",
10473
+ onClick: _cache[2] || (_cache[2] = (0, vue.withModifiers)(($event) => onDiffClick(__props.group.subSteps[0].index), ["stop"]))
10474
+ }, "查看差异")) : (0, vue.createCommentVNode)("v-if", true)
10475
+ ])) : (0, vue.createCommentVNode)("v-if", true),
10476
+ !merged.value && (0, vue.unref)(sourceLabel)(__props.group.source) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9666
10477
  key: 2,
9667
- class: "m-editor-history-list-item-revert",
9668
- title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
9669
- onClick: _cache[0] || (_cache[0] = (0, vue.withModifiers)(($event) => onRevertClick(__props.subSteps[0].index), ["stop"]))
9670
- }, "回滚")) : (0, vue.createCommentVNode)("v-if", true),
9671
- !__props.merged && __props.gotoEnabled && !__props.isCurrent && __props.subSteps.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10478
+ class: "m-editor-history-list-item-source",
10479
+ title: `操作途径:${(0, vue.unref)(sourceLabel)(__props.group.source)}`
10480
+ }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(__props.group.source)), 9, _hoisted_6$3)) : (0, vue.createCommentVNode)("v-if", true),
10481
+ !merged.value && __props.group.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9672
10482
  key: 3,
9673
- class: "m-editor-history-list-item-goto",
9674
- title: "回到该记录",
9675
- onClick: _cache[1] || (_cache[1] = (0, vue.withModifiers)(($event) => onGotoClick(__props.subSteps[0].index), ["stop"]))
9676
- }, "回到")) : (0, vue.createCommentVNode)("v-if", true),
9677
- !__props.merged && headDiffable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9678
- key: 4,
9679
- class: "m-editor-history-list-item-diff",
9680
- title: "查看修改差异",
9681
- onClick: _cache[2] || (_cache[2] = (0, vue.withModifiers)(($event) => onDiffClick(__props.subSteps[0].index), ["stop"]))
9682
- }, "查看差异")) : (0, vue.createCommentVNode)("v-if", true),
9683
- __props.merged ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10483
+ class: "m-editor-history-list-item-time",
10484
+ title: __props.group.timeTitle || __props.group.time
10485
+ }, (0, vue.toDisplayString)(__props.group.time), 9, _hoisted_7$1)) : (0, vue.createCommentVNode)("v-if", true),
10486
+ merged.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8, "合并 " + (0, vue.toDisplayString)(stepCount.value) + " 步", 1)) : (0, vue.createCommentVNode)("v-if", true),
10487
+ merged.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9684
10488
  key: 5,
9685
10489
  class: (0, vue.normalizeClass)(["m-editor-history-list-group-toggle", { "is-expanded": __props.expanded }])
9686
10490
  }, "▾", 2)) : (0, vue.createCommentVNode)("v-if", true)
9687
- ], 10, _hoisted_1$61), __props.merged && __props.expanded ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("ul", _hoisted_6$3, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(subStepsDisplay.value, (s) => {
10491
+ ], 10, _hoisted_1$61), merged.value && __props.expanded ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("ul", _hoisted_9, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(subStepsDisplay.value, (s) => {
9688
10492
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("li", {
9689
10493
  key: s.index,
9690
10494
  class: (0, vue.normalizeClass)({
9691
10495
  "is-undone": !s.applied,
9692
- "is-current": s.isCurrent
10496
+ "is-current": s.isCurrent,
10497
+ "is-clickable": __props.selectEnabled
9693
10498
  }),
9694
- title: subStepTitle(s)
10499
+ title: subStepTitle(s),
10500
+ onClick: ($event) => onSubStepClick(s.index)
9695
10501
  }, [
9696
- (0, vue.createElementVNode)("span", _hoisted_8, "#" + (0, vue.toDisplayString)(s.index + 1), 1),
9697
- (0, vue.createElementVNode)("span", _hoisted_9, (0, vue.toDisplayString)(s.desc), 1),
10502
+ (0, vue.createElementVNode)("span", _hoisted_11, "#" + (0, vue.toDisplayString)(s.index + 1), 1),
10503
+ (0, vue.createElementVNode)("span", _hoisted_12, (0, vue.toDisplayString)(s.desc), 1),
10504
+ s.saved ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_13, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10505
+ s.revertable || s.diffable || __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_14, [
10506
+ s.revertable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10507
+ key: 0,
10508
+ class: "m-editor-history-list-item-revert",
10509
+ title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
10510
+ onClick: (0, vue.withModifiers)(($event) => onRevertClick(s.index), ["stop"])
10511
+ }, "回滚", 8, _hoisted_15)) : (0, vue.createCommentVNode)("v-if", true),
10512
+ __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10513
+ key: 1,
10514
+ class: "m-editor-history-list-item-goto",
10515
+ title: "回到该记录",
10516
+ onClick: (0, vue.withModifiers)(($event) => onGotoClick(s.index), ["stop"])
10517
+ }, "回到", 8, _hoisted_16)) : (0, vue.createCommentVNode)("v-if", true),
10518
+ s.diffable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10519
+ key: 2,
10520
+ class: "m-editor-history-list-item-diff",
10521
+ title: "查看修改差异",
10522
+ onClick: (0, vue.withModifiers)(($event) => onDiffClick(s.index), ["stop"])
10523
+ }, "查看差异", 8, _hoisted_17)) : (0, vue.createCommentVNode)("v-if", true)
10524
+ ])) : (0, vue.createCommentVNode)("v-if", true),
10525
+ (0, vue.unref)(sourceLabel)(s.source) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10526
+ key: 2,
10527
+ class: "m-editor-history-list-item-source",
10528
+ title: `操作途径:${(0, vue.unref)(sourceLabel)(s.source)}`
10529
+ }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(s.source)), 9, _hoisted_18)) : (0, vue.createCommentVNode)("v-if", true),
9698
10530
  s.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9699
- key: 0,
10531
+ key: 3,
9700
10532
  class: "m-editor-history-list-item-time",
9701
10533
  title: s.timeTitle || s.time
9702
- }, (0, vue.toDisplayString)(s.time), 9, _hoisted_10)) : (0, vue.createCommentVNode)("v-if", true),
9703
- s.revertable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9704
- key: 1,
9705
- class: "m-editor-history-list-item-revert",
9706
- title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
9707
- onClick: (0, vue.withModifiers)(($event) => onRevertClick(s.index), ["stop"])
9708
- }, "回滚", 8, _hoisted_11)) : (0, vue.createCommentVNode)("v-if", true),
9709
- __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9710
- key: 2,
9711
- class: "m-editor-history-list-item-goto",
9712
- title: "回到该记录",
9713
- onClick: (0, vue.withModifiers)(($event) => onGotoClick(s.index), ["stop"])
9714
- }, "回到", 8, _hoisted_12)) : (0, vue.createCommentVNode)("v-if", true),
9715
- s.diffable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9716
- key: 3,
9717
- class: "m-editor-history-list-item-diff",
9718
- title: "查看修改差异",
9719
- onClick: (0, vue.withModifiers)(($event) => onDiffClick(s.index), ["stop"])
9720
- }, "查看差异", 8, _hoisted_13)) : (0, vue.createCommentVNode)("v-if", true)
9721
- ], 10, _hoisted_7$1);
10534
+ }, (0, vue.toDisplayString)(s.time), 9, _hoisted_19)) : (0, vue.createCommentVNode)("v-if", true)
10535
+ ], 10, _hoisted_10);
9722
10536
  }), 128))])) : (0, vue.createCommentVNode)("v-if", true)], 2);
9723
10537
  };
9724
10538
  }
@@ -9729,6 +10543,12 @@
9729
10543
  //#endregion
9730
10544
  //#region packages/editor/src/layouts/history-list/InitialRow.vue?vue&type=script&setup=true&lang.ts
9731
10545
  var _hoisted_1$60 = ["title"];
10546
+ var _hoisted_2$22 = { class: "m-editor-history-list-item-desc" };
10547
+ var _hoisted_3$10 = {
10548
+ key: 0,
10549
+ class: "m-editor-history-list-item-actions"
10550
+ };
10551
+ var _hoisted_4$7 = ["title"];
9732
10552
  var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9733
10553
  name: "MEditorHistoryListInitialRow",
9734
10554
  __name: "InitialRow",
@@ -9737,7 +10557,8 @@
9737
10557
  gotoEnabled: {
9738
10558
  type: Boolean,
9739
10559
  default: true
9740
- }
10560
+ },
10561
+ marker: { default: void 0 }
9741
10562
  },
9742
10563
  emits: ["goto-initial"],
9743
10564
  setup(__props, { emit: __emit }) {
@@ -9745,10 +10566,19 @@
9745
10566
  * 「初始状态」记录行:渲染于历史列表底部,作为整个栈的"零点"。
9746
10567
  * - 点击该行会把对应栈撤销到 cursor === 0(即没有任何已应用步骤),等同于回到所有修改之前。
9747
10568
  * - 当对应栈本身已处于 cursor === 0 时(isCurrent=true),用户已在初始状态,点击不再触发动作。
10569
+ * - 当上层传入 `marker`(设置 root 时为该页生成的「未修改的初始状态」标记)时,
10570
+ * 用标记的文案与时间渲染本行;标记不进入撤销/重做栈,仅作为该页基线展示。
9748
10571
  *
9749
10572
  * 该行不是真实 step,仅作为 UI 入口;上层负责把"点击"翻译为 `service.goto*(0)`。
9750
10573
  */
9751
10574
  const props = __props;
10575
+ const desc = (0, vue.computed)(() => props.marker?.historyDescription || "未修改的初始状态");
10576
+ const time = (0, vue.computed)(() => formatHistoryTime(props.marker?.timestamp));
10577
+ const timeTitle = (0, vue.computed)(() => formatHistoryFullTime(props.marker?.timestamp));
10578
+ const rowTitle = (0, vue.computed)(() => {
10579
+ const base = props.marker?.historyDescription || "未修改的初始状态";
10580
+ return props.isCurrent ? `当前已回到${base}` : `点击回到${base}`;
10581
+ });
9752
10582
  const emit = __emit;
9753
10583
  const onClick = () => {
9754
10584
  if (props.isCurrent) return;
@@ -9760,20 +10590,24 @@
9760
10590
  "is-current": __props.isCurrent,
9761
10591
  "is-clickable": !__props.isCurrent
9762
10592
  }]),
9763
- title: __props.isCurrent ? "当前已回到未修改的初始状态" : "点击回到未修改的初始状态"
10593
+ title: rowTitle.value
9764
10594
  }, [
9765
10595
  _cache[0] || (_cache[0] = (0, vue.createElementVNode)("span", {
9766
10596
  class: "m-editor-history-list-item-index",
9767
10597
  title: "历史步骤编号 #0(未修改的初始状态)"
9768
10598
  }, "#0", -1)),
9769
10599
  _cache[1] || (_cache[1] = (0, vue.createElementVNode)("span", { class: "m-editor-history-list-item-op op-initial" }, "初始", -1)),
9770
- _cache[2] || (_cache[2] = (0, vue.createElementVNode)("span", { class: "m-editor-history-list-item-desc" }, "未修改的初始状态", -1)),
9771
- __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9772
- key: 0,
10600
+ (0, vue.createElementVNode)("span", _hoisted_2$22, (0, vue.toDisplayString)(desc.value), 1),
10601
+ __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$10, [(0, vue.createElementVNode)("span", {
9773
10602
  class: "m-editor-history-list-item-goto",
9774
10603
  title: "回到该记录",
9775
10604
  onClick: (0, vue.withModifiers)(onClick, ["stop"])
9776
- }, "回到")) : (0, vue.createCommentVNode)("v-if", true)
10605
+ }, "回到")])) : (0, vue.createCommentVNode)("v-if", true),
10606
+ time.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10607
+ key: 1,
10608
+ class: "m-editor-history-list-item-time",
10609
+ title: timeTitle.value
10610
+ }, (0, vue.toDisplayString)(time.value), 9, _hoisted_4$7)) : (0, vue.createCommentVNode)("v-if", true)
9777
10611
  ], 10, _hoisted_1$60);
9778
10612
  };
9779
10613
  }
@@ -9784,29 +10618,17 @@
9784
10618
  //#endregion
9785
10619
  //#region packages/editor/src/layouts/history-list/Bucket.vue?vue&type=script&setup=true&lang.ts
9786
10620
  var _hoisted_1$59 = { class: "m-editor-history-list-bucket" };
9787
- var _hoisted_2$20 = { class: "m-editor-history-list-bucket-title" };
9788
- var _hoisted_3$7 = { class: "m-editor-history-list-bucket-count" };
10621
+ var _hoisted_2$21 = { class: "m-editor-history-list-bucket-title" };
10622
+ var _hoisted_3$9 = { class: "m-editor-history-list-bucket-count" };
9789
10623
  var _hoisted_4$6 = { class: "m-editor-history-list-ul" };
9790
10624
  var Bucket_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9791
10625
  name: "MEditorHistoryListBucket",
9792
10626
  __name: "Bucket",
9793
10627
  props: {
9794
- title: {},
10628
+ config: {},
9795
10629
  bucketId: {},
9796
- prefix: {},
9797
- showInitial: {
9798
- type: Boolean,
9799
- default: true
9800
- },
9801
10630
  groups: {},
9802
- describeGroup: {},
9803
- describeStep: {},
9804
- isStepDiffable: {},
9805
- expanded: {},
9806
- gotoEnabled: {
9807
- type: Boolean,
9808
- default: true
9809
- }
10631
+ expanded: {}
9810
10632
  },
9811
10633
  emits: [
9812
10634
  "toggle",
@@ -9817,62 +10639,42 @@
9817
10639
  ],
9818
10640
  setup(__props) {
9819
10641
  const props = __props;
10642
+ /**
10643
+ * 子项 / 折叠状态 key:`${prefix}-${bucketId}-${组内首步 index}`。
10644
+ * 以稳定的 step 索引(而非展示位置)标识分组,历史数据更新后已展开的分组状态仍能正确保持。
10645
+ */
10646
+ const rowKey = (group) => `${props.config.prefix}-${props.bucketId}-${group.steps[0]?.index}`;
10647
+ /** 把原始分组派生为 GroupRow 直接消费的视图模型。 */
10648
+ const toRow = (group) => toRowGroup(group, rowKey(group), props.config);
9820
10649
  /** 该 bucket 是否处于初始状态(栈 cursor=0),等价于全部 group 都未 applied。 */
9821
10650
  const isInitial = (0, vue.computed)(() => props.groups.length > 0 && props.groups.every((g) => !g.applied));
9822
10651
  return (_ctx, _cache) => {
9823
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$59, [(0, vue.createElementVNode)("div", _hoisted_2$20, [
9824
- (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.title), 1),
10652
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$59, [(0, vue.createElementVNode)("div", _hoisted_2$21, [
10653
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.config.title), 1),
9825
10654
  (0, vue.createElementVNode)("code", null, (0, vue.toDisplayString)(String(__props.bucketId)), 1),
9826
- (0, vue.createElementVNode)("span", _hoisted_3$7, (0, vue.toDisplayString)(__props.groups.length) + " 组", 1)
10655
+ (0, vue.createElementVNode)("span", _hoisted_3$9, (0, vue.toDisplayString)(__props.groups.length) + " 组", 1)
9827
10656
  ]), (0, vue.createElementVNode)("ul", _hoisted_4$6, [
9828
10657
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.groups, (group) => {
9829
10658
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupRow_default, {
9830
- key: `${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`,
9831
- "group-key": `${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`,
9832
- applied: group.applied,
9833
- merged: group.steps.length > 1,
9834
- "op-type": group.opType,
9835
- desc: __props.describeGroup(group),
9836
- time: (0, vue.unref)(formatHistoryTime)((0, vue.unref)(groupTimestamp)(group)),
9837
- "time-title": (0, vue.unref)(formatHistoryFullTime)((0, vue.unref)(groupTimestamp)(group)),
9838
- "step-count": group.steps.length,
9839
- "sub-steps": group.steps.map((s) => ({
9840
- index: s.index,
9841
- applied: s.applied,
9842
- isCurrent: s.isCurrent,
9843
- desc: __props.describeStep(s.step),
9844
- diffable: __props.isStepDiffable ? __props.isStepDiffable(s.step) : false,
9845
- revertable: s.applied,
9846
- time: (0, vue.unref)(formatHistoryTime)(s.step.timestamp),
9847
- timeTitle: (0, vue.unref)(formatHistoryFullTime)(s.step.timestamp)
9848
- })),
9849
- "is-current": group.isCurrent,
9850
- expanded: !!__props.expanded[`${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`],
9851
- "goto-enabled": __props.gotoEnabled,
10659
+ key: rowKey(group),
10660
+ group: toRow(group),
10661
+ expanded: (0, vue.unref)(isHistoryGroupExpanded)(__props.expanded, rowKey(group)),
10662
+ "goto-enabled": __props.config.gotoEnabled,
9852
10663
  onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
9853
10664
  onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", __props.bucketId, index)),
9854
10665
  onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", __props.bucketId, index)),
9855
10666
  onRevertStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("revert-step", __props.bucketId, index))
9856
10667
  }, null, 8, [
9857
- "group-key",
9858
- "applied",
9859
- "merged",
9860
- "op-type",
9861
- "desc",
9862
- "time",
9863
- "time-title",
9864
- "step-count",
9865
- "sub-steps",
9866
- "is-current",
10668
+ "group",
9867
10669
  "expanded",
9868
10670
  "goto-enabled"
9869
10671
  ]);
9870
10672
  }), 128)),
9871
- (0, vue.createCommentVNode)("\n 初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。\n 当 bucket 内所有 group 都未 applied 时即为当前位置。\n showInitial=false 时不展示(用于没有\"撤销到初始状态\"语义的自定义历史,如业务模块历史)。\n "),
9872
- __props.showInitial !== false ? ((0, vue.openBlock)(), (0, vue.createBlock)(InitialRow_default, {
10673
+ (0, vue.createCommentVNode)("\n 初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。\n 当 bucket 内所有 group 都未 applied 时即为当前位置。\n config.showInitial=false 时不展示(用于没有\"撤销到初始状态\"语义的自定义历史,如业务模块历史)。\n "),
10674
+ __props.config.showInitial !== false ? ((0, vue.openBlock)(), (0, vue.createBlock)(InitialRow_default, {
9873
10675
  key: 0,
9874
10676
  "is-current": isInitial.value,
9875
- "goto-enabled": __props.gotoEnabled,
10677
+ "goto-enabled": __props.config.gotoEnabled,
9876
10678
  onGotoInitial: _cache[4] || (_cache[4] = ($event) => _ctx.$emit("goto-initial", __props.bucketId))
9877
10679
  }, null, 8, ["is-current", "goto-enabled"])) : (0, vue.createCommentVNode)("v-if", true)
9878
10680
  ])]);
@@ -9888,66 +10690,52 @@
9888
10690
  key: 0,
9889
10691
  class: "m-editor-history-list-empty"
9890
10692
  };
10693
+ var _hoisted_2$20 = { class: "m-editor-history-list-toolbar" };
10694
+ var _hoisted_3$8 = ["title"];
9891
10695
  var BucketTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9892
10696
  name: "MEditorHistoryListBucketTab",
9893
10697
  __name: "BucketTab",
9894
10698
  props: {
9895
- title: {},
9896
- prefix: {},
10699
+ config: {},
9897
10700
  buckets: {},
9898
- describeGroup: {},
9899
- describeStep: {},
9900
- isStepDiffable: {},
9901
- expanded: {},
9902
- gotoEnabled: {
9903
- type: Boolean,
9904
- default: true
9905
- }
10701
+ expanded: {}
9906
10702
  },
9907
10703
  emits: [
9908
10704
  "toggle",
9909
10705
  "goto",
9910
10706
  "goto-initial",
9911
10707
  "diff-step",
9912
- "revert-step"
10708
+ "revert-step",
10709
+ "clear"
9913
10710
  ],
9914
10711
  setup(__props) {
9915
10712
  return (_ctx, _cache) => {
9916
- return !__props.buckets.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$58, "暂无操作记录")) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicScrollbar), {
9917
- key: 1,
9918
- "max-height": "360px"
9919
- }, {
10713
+ return !__props.buckets.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$58, "暂无操作记录")) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [(0, vue.createElementVNode)("div", _hoisted_2$20, [(0, vue.createElementVNode)("span", {
10714
+ class: "m-editor-history-list-clear",
10715
+ title: `清空${__props.config.title}的历史记录`,
10716
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
10717
+ }, "清空", 8, _hoisted_3$8)]), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicScrollbar), { "max-height": "360px" }, {
9920
10718
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.buckets, (bucket) => {
9921
10719
  return (0, vue.openBlock)(), (0, vue.createBlock)(Bucket_default, {
9922
- key: `${__props.prefix}-${bucket.id}`,
9923
- title: __props.title,
10720
+ key: `${__props.config.prefix}-${bucket.id}`,
10721
+ config: __props.config,
9924
10722
  "bucket-id": bucket.id,
9925
- prefix: __props.prefix,
9926
10723
  groups: bucket.groups,
9927
- "describe-group": __props.describeGroup,
9928
- "describe-step": __props.describeStep,
9929
- "is-step-diffable": __props.isStepDiffable,
9930
10724
  expanded: __props.expanded,
9931
- "goto-enabled": __props.gotoEnabled,
9932
- onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
9933
- onGoto: _cache[1] || (_cache[1] = (id, index) => _ctx.$emit("goto", id, index)),
9934
- onGotoInitial: _cache[2] || (_cache[2] = (id) => _ctx.$emit("goto-initial", id)),
9935
- onDiffStep: _cache[3] || (_cache[3] = (id, index) => _ctx.$emit("diff-step", id, index)),
9936
- onRevertStep: _cache[4] || (_cache[4] = (id, index) => _ctx.$emit("revert-step", id, index))
10725
+ onToggle: _cache[1] || (_cache[1] = (key) => _ctx.$emit("toggle", key)),
10726
+ onGoto: _cache[2] || (_cache[2] = (id, index) => _ctx.$emit("goto", id, index)),
10727
+ onGotoInitial: _cache[3] || (_cache[3] = (id) => _ctx.$emit("goto-initial", id)),
10728
+ onDiffStep: _cache[4] || (_cache[4] = (id, index) => _ctx.$emit("diff-step", id, index)),
10729
+ onRevertStep: _cache[5] || (_cache[5] = (id, index) => _ctx.$emit("revert-step", id, index))
9937
10730
  }, null, 8, [
9938
- "title",
10731
+ "config",
9939
10732
  "bucket-id",
9940
- "prefix",
9941
10733
  "groups",
9942
- "describe-group",
9943
- "describe-step",
9944
- "is-step-diffable",
9945
- "expanded",
9946
- "goto-enabled"
10734
+ "expanded"
9947
10735
  ]);
9948
10736
  }), 128))]),
9949
10737
  _: 1
9950
- }));
10738
+ })], 64));
9951
10739
  };
9952
10740
  }
9953
10741
  });
@@ -10030,6 +10818,20 @@
10030
10818
  }
10031
10819
  return !isEqual(curValue, lastValue);
10032
10820
  };
10821
+ const removeStyleDisplayConfig = (formConfig) => formConfig.map((item) => {
10822
+ if (!("type" in item)) return item;
10823
+ if (item?.type !== "tab" || !Array.isArray(item.items)) return item;
10824
+ return {
10825
+ ...item,
10826
+ items: item.items.map((tabPane) => {
10827
+ if (tabPane?.title !== "样式" || !Array.isArray(tabPane.items)) return tabPane;
10828
+ return {
10829
+ ...tabPane,
10830
+ display: true
10831
+ };
10832
+ })
10833
+ };
10834
+ });
10033
10835
  /**
10034
10836
  * 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
10035
10837
  * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
@@ -10038,7 +10840,7 @@
10038
10840
  switch (props.category) {
10039
10841
  case "node":
10040
10842
  if (!props.type) return [];
10041
- return await propsService.getPropsConfig(props.type);
10843
+ return removeStyleDisplayConfig(await propsService.getPropsConfig(props.type, { node: props.value }));
10042
10844
  case "data-source": return dataSourceService.getFormConfig(props.type || "base");
10043
10845
  case "code-block": return getCodeBlockFormConfig({
10044
10846
  paramColConfig: codeBlockService.getParamsColConfig(),
@@ -10131,7 +10933,7 @@
10131
10933
  key: 0,
10132
10934
  class: "m-editor-history-diff-dialog-notice"
10133
10935
  };
10134
- var _hoisted_3$6 = { class: "m-editor-history-diff-dialog-header" };
10936
+ var _hoisted_3$7 = { class: "m-editor-history-diff-dialog-header" };
10135
10937
  var _hoisted_4$5 = { class: "m-editor-history-diff-dialog-target" };
10136
10938
  var _hoisted_5$2 = { class: "m-editor-history-diff-dialog-controls" };
10137
10939
  var _hoisted_6$2 = { class: "m-editor-history-diff-dialog-legend" };
@@ -10146,6 +10948,7 @@
10146
10948
  extendState: {},
10147
10949
  loadConfig: {},
10148
10950
  width: { default: "900px" },
10951
+ isConfirm: { type: Boolean },
10149
10952
  onConfirm: {},
10150
10953
  selfDiffFieldTypes: {}
10151
10954
  },
@@ -10200,18 +11003,23 @@
10200
11003
  if (mode.value !== "current" || !payload.value) return false;
10201
11004
  return isEqual(payload.value.value, payload.value.currentValue);
10202
11005
  });
11006
+ /** confirm() 的 resolve,仅在「等待用户确认回滚」期间存在 */
11007
+ let confirmResolve = null;
10203
11008
  const onConfirmClick = () => {
10204
- const cb = props.onConfirm;
10205
- cb?.();
11009
+ props.onConfirm?.();
11010
+ confirmResolve?.(true);
11011
+ confirmResolve = null;
10206
11012
  visible.value = false;
10207
11013
  };
10208
11014
  const targetText = (0, vue.computed)(() => {
10209
11015
  if (!payload.value) return "";
10210
- const prefix = {
11016
+ const categoryText = {
10211
11017
  node: "节点",
10212
11018
  "data-source": "数据源",
10213
11019
  "code-block": "代码块"
10214
- }[payload.value.category] || "";
11020
+ };
11021
+ const { category } = payload.value;
11022
+ const prefix = category ? categoryText[category] : "";
10215
11023
  const label = payload.value.targetLabel || payload.value.type || "";
10216
11024
  const { id } = payload.value;
10217
11025
  return [prefix, id !== void 0 && id !== "" ? `${label}(${id})` : label].filter(Boolean).join(":");
@@ -10222,21 +11030,41 @@
10222
11030
  viewMode.value = "form";
10223
11031
  visible.value = true;
10224
11032
  };
11033
+ /**
11034
+ * 以 Promise 形式打开确认回滚弹窗:
11035
+ * - 用户点击「确定回滚」时 resolve(true);
11036
+ * - 取消 / 关闭 / 按 Esc 等其他方式关闭弹窗时 resolve(false)。
11037
+ *
11038
+ * 同一时刻只允许一个待确认流程,重复调用会先 resolve(false) 掉上一个。
11039
+ */
11040
+ const confirm = (p) => {
11041
+ confirmResolve?.(false);
11042
+ confirmResolve = null;
11043
+ return new Promise((resolve) => {
11044
+ confirmResolve = resolve;
11045
+ open(p);
11046
+ });
11047
+ };
10225
11048
  const close = () => {
10226
11049
  visible.value = false;
10227
11050
  };
10228
11051
  (0, vue.watch)(visible, (v) => {
10229
- if (!v) payload.value = null;
11052
+ if (!v) {
11053
+ payload.value = null;
11054
+ confirmResolve?.(false);
11055
+ confirmResolve = null;
11056
+ }
10230
11057
  });
10231
11058
  const onClose = () => {
10232
11059
  emit("close");
10233
11060
  };
10234
11061
  __expose({
10235
11062
  open,
11063
+ confirm,
10236
11064
  close
10237
11065
  });
10238
11066
  return (_ctx, _cache) => {
10239
- return (0, vue.openBlock)(), (0, vue.createBlock)(vue.Teleport, { to: "body" }, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicDialog), {
11067
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDialog), {
10240
11068
  modelValue: visible.value,
10241
11069
  "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => visible.value = $event),
10242
11070
  class: "m-editor-history-diff-dialog",
@@ -10247,7 +11075,7 @@
10247
11075
  width: __props.width,
10248
11076
  onClose
10249
11077
  }, {
10250
- footer: (0, vue.withCtx)(() => [__props.onConfirm ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
11078
+ footer: (0, vue.withCtx)(() => [__props.isConfirm ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
10251
11079
  size: "small",
10252
11080
  onClick: _cache[2] || (_cache[2] = ($event) => visible.value = false)
10253
11081
  }, {
@@ -10268,9 +11096,9 @@
10268
11096
  default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("关闭", -1)])]),
10269
11097
  _: 1
10270
11098
  }))]),
10271
- default: (0, vue.withCtx)(() => [payload.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$57, [
11099
+ default: (0, vue.withCtx)(() => [payload.value && visible.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$57, [
10272
11100
  __props.onConfirm ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$19, "仅回滚有差异的字段")) : (0, vue.createCommentVNode)("v-if", true),
10273
- (0, vue.createElementVNode)("div", _hoisted_3$6, [(0, vue.createElementVNode)("span", _hoisted_4$5, (0, vue.toDisplayString)(targetText.value), 1), (0, vue.createElementVNode)("div", _hoisted_5$2, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicRadioGroup), {
11101
+ (0, vue.createElementVNode)("div", _hoisted_3$7, [(0, vue.createElementVNode)("span", _hoisted_4$5, (0, vue.toDisplayString)(targetText.value), 1), (0, vue.createElementVNode)("div", _hoisted_5$2, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicRadioGroup), {
10274
11102
  modelValue: viewMode.value,
10275
11103
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => viewMode.value = $event),
10276
11104
  size: "small",
@@ -10356,7 +11184,7 @@
10356
11184
  "modelValue",
10357
11185
  "title",
10358
11186
  "width"
10359
- ])]);
11187
+ ]);
10360
11188
  };
10361
11189
  }
10362
11190
  });
@@ -10369,96 +11197,86 @@
10369
11197
  key: 0,
10370
11198
  class: "m-editor-history-list-empty"
10371
11199
  };
10372
- var _hoisted_2$18 = { class: "m-editor-history-list-ul" };
11200
+ var _hoisted_2$18 = {
11201
+ key: 0,
11202
+ class: "m-editor-history-list-toolbar"
11203
+ };
11204
+ var _hoisted_3$6 = { class: "m-editor-history-list-ul" };
10373
11205
  var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10374
11206
  name: "MEditorHistoryListPageTab",
10375
11207
  __name: "PageTab",
10376
11208
  props: {
10377
11209
  list: {},
10378
- expanded: {}
11210
+ expanded: {},
11211
+ marker: {}
10379
11212
  },
10380
11213
  emits: [
10381
11214
  "toggle",
10382
11215
  "goto",
10383
11216
  "goto-initial",
10384
11217
  "diff-step",
10385
- "revert-step"
11218
+ "revert-step",
11219
+ "select",
11220
+ "clear"
10386
11221
  ],
10387
11222
  setup(__props) {
10388
11223
  const props = __props;
10389
11224
  /**
10390
11225
  * 当前 step 是否可查看差异:
10391
11226
  * - 仅 update 操作;
10392
- * - 单节点更新(updatedItems.length === 1),且 oldNode / newNode 都存在。
11227
+ * - 单节点更新(diff.length === 1),且 oldSchema / newSchema 都存在。
10393
11228
  * 多节点更新难以选定单一对比目标,统一不展示差异入口。
10394
11229
  */
10395
11230
  const isPageStepDiffable = (step) => {
10396
11231
  if (step.opType !== "update") return false;
10397
- const items = step.updatedItems ?? [];
11232
+ const items = step.diff ?? [];
10398
11233
  if (items.length !== 1) return false;
10399
- return Boolean(items[0]?.oldNode && items[0]?.newNode);
11234
+ return Boolean(items[0]?.oldSchema && items[0]?.newSchema);
10400
11235
  };
11236
+ /** 页面历史的描述 / 可操作性判定集合,注入给统一的 `toRowGroup`。 */
11237
+ const descriptor = {
11238
+ describeGroup: describePageGroup,
11239
+ describeStep: describePageStep,
11240
+ isStepDiffable: isPageStepDiffable,
11241
+ isStepRevertable: isPageStepRevertable
11242
+ };
11243
+ const rowKey = (group) => `pg-${group.steps[0]?.index}`;
11244
+ const toRow = (group) => toRowGroup(group, rowKey(group), descriptor);
10401
11245
  /**
10402
11246
  * 是否处于"初始状态"——即对应页面历史栈 cursor===0:
10403
- * 当 list 中所有 group 的 applied 都为 false 时即为该状态。
10404
- * 没有任何 group 的情况由外层"暂无操作记录"分支兜底,本计算可以不考虑。
11247
+ * 当 list 中所有 group 的 applied 都为 false 时即为该状态(空列表 `every` 返回 true,
11248
+ * 即仅有 marker、无任何操作记录时也视为处于初始状态)。
10405
11249
  */
10406
- const isInitial = (0, vue.computed)(() => props.list.length > 0 && props.list.every((g) => !g.applied));
11250
+ const isInitial = (0, vue.computed)(() => props.list.every((g) => !g.applied));
10407
11251
  return (_ctx, _cache) => {
10408
- return !__props.list.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$56, "暂无操作记录")) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicScrollbar), {
10409
- key: 1,
10410
- "max-height": "360px"
10411
- }, {
10412
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("ul", _hoisted_2$18, [
11252
+ return !__props.list.length && !__props.marker ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$56, "暂无操作记录")) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [__props.list.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$18, [(0, vue.createElementVNode)("span", {
11253
+ class: "m-editor-history-list-clear",
11254
+ title: "清空当前页面的历史记录",
11255
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
11256
+ }, "清空")])) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicScrollbar), { "max-height": "360px" }, {
11257
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("ul", _hoisted_3$6, [
10413
11258
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.list, (group) => {
10414
11259
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupRow_default, {
10415
- key: `pg-${group.steps[0]?.index}`,
10416
- "group-key": `pg-${group.steps[0]?.index}`,
10417
- applied: group.applied,
10418
- merged: group.steps.length > 1,
10419
- "op-type": group.opType,
10420
- desc: (0, vue.unref)(describePageGroup)(group),
10421
- time: (0, vue.unref)(formatHistoryTime)((0, vue.unref)(groupTimestamp)(group)),
10422
- "time-title": (0, vue.unref)(formatHistoryFullTime)((0, vue.unref)(groupTimestamp)(group)),
10423
- "step-count": group.steps.length,
10424
- "sub-steps": group.steps.map((s) => ({
10425
- index: s.index,
10426
- applied: s.applied,
10427
- isCurrent: s.isCurrent,
10428
- desc: (0, vue.unref)(describePageStep)(s.step),
10429
- diffable: isPageStepDiffable(s.step),
10430
- revertable: s.applied,
10431
- time: (0, vue.unref)(formatHistoryTime)(s.step.timestamp),
10432
- timeTitle: (0, vue.unref)(formatHistoryFullTime)(s.step.timestamp)
10433
- })),
10434
- "is-current": group.isCurrent,
10435
- expanded: !!__props.expanded[`pg-${group.steps[0]?.index}`],
10436
- onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
10437
- onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", index)),
10438
- onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", index)),
10439
- onRevertStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("revert-step", index))
10440
- }, null, 8, [
10441
- "group-key",
10442
- "applied",
10443
- "merged",
10444
- "op-type",
10445
- "desc",
10446
- "time",
10447
- "time-title",
10448
- "step-count",
10449
- "sub-steps",
10450
- "is-current",
10451
- "expanded"
10452
- ]);
11260
+ key: rowKey(group),
11261
+ group: toRow(group),
11262
+ expanded: (0, vue.unref)(isHistoryGroupExpanded)(__props.expanded, rowKey(group)),
11263
+ "select-enabled": true,
11264
+ onToggle: _cache[1] || (_cache[1] = (key) => _ctx.$emit("toggle", key)),
11265
+ onGoto: _cache[2] || (_cache[2] = (index) => _ctx.$emit("goto", index)),
11266
+ onDiffStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("diff-step", index)),
11267
+ onRevertStep: _cache[4] || (_cache[4] = (index) => _ctx.$emit("revert-step", index)),
11268
+ onSelect: _cache[5] || (_cache[5] = (index) => _ctx.$emit("select", index))
11269
+ }, null, 8, ["group", "expanded"]);
10453
11270
  }), 128)),
10454
- (0, vue.createCommentVNode)("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n "),
11271
+ (0, vue.createCommentVNode)("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n 设置 root 时生成的「未修改的初始状态」标记(marker)会作为该行的文案与时间来源。\n "),
10455
11272
  (0, vue.createVNode)(InitialRow_default, {
10456
11273
  "is-current": isInitial.value,
10457
- onGotoInitial: _cache[4] || (_cache[4] = ($event) => _ctx.$emit("goto-initial"))
10458
- }, null, 8, ["is-current"])
11274
+ marker: __props.marker,
11275
+ onGotoInitial: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("goto-initial"))
11276
+ }, null, 8, ["is-current", "marker"])
10459
11277
  ])]),
10460
11278
  _: 1
10461
- }));
11279
+ })], 64));
10462
11280
  };
10463
11281
  }
10464
11282
  });
@@ -10468,6 +11286,12 @@
10468
11286
  //#endregion
10469
11287
  //#region packages/editor/src/layouts/history-list/HistoryListPanel.vue?vue&type=script&setup=true&lang.ts
10470
11288
  var _hoisted_1$55 = { class: "m-editor-history-list" };
11289
+ /**
11290
+ * 构造差异弹窗入参:仅 update(前后值都存在)可对比。
11291
+ * - 页面(无 id):在全部分组中按 index 定位 step,目标 id 取自快照;
11292
+ * - 数据源 / 代码块(带 id):先匹配分组 id 再按 index 定位。
11293
+ * 无可对比内容(多节点 / add / remove)或定位不到时返回 null。
11294
+ */
10471
11295
  var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10472
11296
  name: "MEditorHistoryListPanel",
10473
11297
  __name: "HistoryListPanel",
@@ -10496,7 +11320,10 @@
10496
11320
  const ClockIcon = (0, vue.markRaw)(_element_plus_icons_vue.Clock);
10497
11321
  const CloseIcon = (0, vue.markRaw)(_element_plus_icons_vue.Close);
10498
11322
  const activeTab = (0, vue.ref)("page");
10499
- /** 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。 */
11323
+ /**
11324
+ * 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。
11325
+ * 点击面板以外区域的自动收起由 TMagicPopover 通过 v-model:visible 回写完成。
11326
+ */
10500
11327
  const visible = (0, vue.ref)(false);
10501
11328
  const tabPaneComponent = (0, _tmagic_design.getDesignConfig)("components")?.tabPane;
10502
11329
  /**
@@ -10506,7 +11333,7 @@
10506
11333
  const extraTabs = (0, vue.inject)("historyListExtraTabs", []);
10507
11334
  /** label 支持字符串或函数,函数形式便于展示动态数量等内容。 */
10508
11335
  const resolveTabLabel = (tab) => typeof tab.label === "function" ? tab.label() : tab.label;
10509
- const { editorService, dataSourceService, codeBlockService, historyService, propsService } = useServices();
11336
+ const { editorService, dataSourceService, codeBlockService, historyService, propsService, stageOverlayService } = useServices();
10510
11337
  /**
10511
11338
  * 数据源 / 代码块功能可被业务方通过 `disabledDataSource` / `disabledCodeBlock` 禁用,
10512
11339
  * 禁用后对应的历史记录 tab 不再展示。若当前激活的 tab 恰好被禁用,则回退到「页面」tab。
@@ -10523,15 +11350,60 @@
10523
11350
  */
10524
11351
  const extendFormState = (0, vue.inject)("extendFormState", void 0);
10525
11352
  const { expanded, toggleGroup, pageGroups, dataSourceGroups, codeBlockGroups, pageGroupsDisplay, dataSourceGroupsByTarget, codeBlockGroupsByTarget } = useHistoryList();
11353
+ /**
11354
+ * 当前活动页的「加载/初始」标记记录(设置 root 时生成),透传给 PageTab 的底部初始行展示。
11355
+ * 基于 historyService 的 reactive state 派生,活动页切换或标记写入后自动刷新。
11356
+ */
11357
+ const pageMarker = (0, vue.computed)(() => historyService.getPageMarker());
10526
11358
  /** 数据源 step 仅 update(前后 schema 都存在)时可查看差异。 */
10527
- const isDataSourceStepDiffable = (step) => Boolean(step.oldSchema && step.newSchema);
11359
+ const isDataSourceStepDiffable = (step) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
10528
11360
  /** 代码块 step 仅 update(前后 content 都存在)时可查看差异。 */
10529
- const isCodeBlockStepDiffable = (step) => Boolean(step.oldContent && step.newContent);
11361
+ const isCodeBlockStepDiffable = (step) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
11362
+ /**
11363
+ * 数据源 / 代码块两类 bucket 历史的整体渲染配置:把 title / prefix 与各自的描述、
11364
+ * 可差异、可回滚判定收敛为单一对象整体注入 BucketTab,组件内部按需读取。
11365
+ */
11366
+ const dataSourceConfig = {
11367
+ title: "数据源",
11368
+ prefix: "ds",
11369
+ describeGroup: describeDataSourceGroup,
11370
+ describeStep: describeDataSourceStep,
11371
+ isStepDiffable: isDataSourceStepDiffable,
11372
+ isStepRevertable: isDataSourceStepRevertable
11373
+ };
11374
+ const codeBlockConfig = {
11375
+ title: "代码块",
11376
+ prefix: "cb",
11377
+ describeGroup: describeCodeBlockGroup,
11378
+ describeStep: describeCodeBlockStep,
11379
+ isStepDiffable: isCodeBlockStepDiffable,
11380
+ isStepRevertable: isCodeBlockStepRevertable
11381
+ };
10530
11382
  /** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
10531
11383
  const indexToCursor = (index) => index + 1;
10532
11384
  const onPageGoto = (index) => {
10533
11385
  editorService.gotoPageStep(indexToCursor(index));
10534
11386
  };
11387
+ /**
11388
+ * 点击页面历史记录行:选中该记录对应的画布节点。
11389
+ * - 从目标 step 的 diff 中取节点 id(优先 newSchema,回退 oldSchema),按出现顺序找到第一个当前仍存在的节点;
11390
+ * - 与图层树点击选中一致:editorService.select + 画布 / overlay 画布 select 三者联动;
11391
+ * - 该 step 涉及的节点都已不存在(如删除记录、被撤销的新增)时给出提示,不做选中。
11392
+ */
11393
+ const onPageSelect = async (index) => {
11394
+ const step = historyService.getPageStepList()[index]?.step;
11395
+ if (!step) return;
11396
+ const targetId = (step.diff ?? []).map((item) => item.newSchema?.id ?? item.oldSchema?.id).find((id) => id !== void 0 && id !== null && editorService.getNodeById(id, false));
11397
+ if (targetId === void 0 || targetId === null) {
11398
+ _tmagic_design.tMagicMessage.warning("该记录对应的节点已不存在,无法选中");
11399
+ return;
11400
+ }
11401
+ const node = editorService.getNodeById(targetId, false);
11402
+ if (!node) return;
11403
+ await editorService.select(node);
11404
+ editorService.get("stage")?.select(targetId);
11405
+ stageOverlayService.get("stage")?.select(targetId);
11406
+ };
10535
11407
  const onDataSourceGoto = (id, index) => {
10536
11408
  dataSourceService.goto(id, indexToCursor(index));
10537
11409
  };
@@ -10552,71 +11424,53 @@
10552
11424
  codeBlockService.goto(id, 0);
10553
11425
  };
10554
11426
  const diffDialogRef = (0, vue.useTemplateRef)("diffDialog");
11427
+ const confirmDialogRef = (0, vue.useTemplateRef)("confirmDialog");
10555
11428
  /**
10556
- * 构造页面 step 的差异弹窗入参:仅 update 单节点修改可对比,传入旧/新节点。
10557
- * 节点类型 `type` 优先取 newNode.type,再回退 oldNode.type。
10558
- * `currentValue` 取自 editorService 中该节点当前实际值,用于支持「与当前对比」。
10559
- * 无可对比内容(如多节点 / add / remove)时返回 null。
11429
+ * 三类历史(页面 / 数据源 / 代码块)差异弹窗入参的构造差异,收敛为一份配置:
11430
+ * 仅「分组来源、当前值读取、类型 / 展示名提取」不同,定位 step、校验前后值、组装 payload 的流程共用。
10560
11431
  */
10561
- const buildPageDiffPayload = (index) => {
10562
- const groups = historyService.getPageHistoryGroups();
10563
- for (const group of groups) {
10564
- const entry = group.steps.find((s) => s.index === index);
10565
- if (!entry) continue;
10566
- const item = entry.step.updatedItems?.[0];
10567
- if (!item?.oldNode || !item?.newNode) return null;
10568
- const type = item.newNode.type || item.oldNode.type || "";
10569
- const nodeId = item.newNode.id ?? item.oldNode.id;
10570
- const currentNode = nodeId !== void 0 ? editorService.getNodeById(nodeId) : null;
11432
+ const buildDiffPayload = (source, index, id) => {
11433
+ for (const group of source.groups()) {
11434
+ if (id !== void 0 && group.id !== id) continue;
11435
+ const step = group.steps.find((s) => s.index === index)?.step;
11436
+ if (!step) continue;
11437
+ const oldSchema = step.diff?.[0]?.oldSchema;
11438
+ const newSchema = step.diff?.[0]?.newSchema;
11439
+ if (!oldSchema || !newSchema) return null;
11440
+ const targetId = id ?? newSchema.id ?? oldSchema.id;
11441
+ const type = source.resolveType?.(newSchema, oldSchema);
10571
11442
  return {
10572
- category: "node",
10573
- type,
10574
- lastValue: item.oldNode,
10575
- value: item.newNode,
10576
- currentValue: currentNode || null,
10577
- targetLabel: item.newNode.name || item.oldNode.name || type,
10578
- id: nodeId
11443
+ category: source.category,
11444
+ ...type !== void 0 ? { type } : {},
11445
+ lastValue: oldSchema,
11446
+ value: newSchema,
11447
+ currentValue: (targetId !== void 0 ? source.getCurrent(targetId) : null) || null,
11448
+ targetLabel: source.resolveLabel(newSchema, oldSchema, targetId),
11449
+ id: targetId
10579
11450
  };
10580
11451
  }
10581
11452
  return null;
10582
11453
  };
10583
- /**
10584
- * 在指定分组列表中按 id / index 查找命中的 step,命中后交由 build 构造差异弹窗入参。
10585
- * 用于统一数据源、代码块两类历史的查找逻辑。
10586
- */
10587
- const findGroupStep = (groups, id, index, build) => {
10588
- for (const group of groups) {
10589
- if (group.id !== id) continue;
10590
- const entry = group.steps.find((s) => s.index === index);
10591
- if (!entry) continue;
10592
- return build(entry.step);
10593
- }
10594
- return null;
10595
- };
10596
- const buildDataSourceDiffPayload = (id, index) => findGroupStep(historyService.getDataSourceHistoryGroups(), id, index, ({ oldSchema, newSchema }) => {
10597
- if (!oldSchema || !newSchema) return null;
10598
- const currentSchema = dataSourceService.getDataSourceById(`${id}`);
10599
- return {
10600
- category: "data-source",
10601
- type: newSchema.type || oldSchema.type || "base",
10602
- lastValue: oldSchema,
10603
- value: newSchema,
10604
- currentValue: currentSchema || null,
10605
- targetLabel: newSchema.title || oldSchema.title || `${id}`,
10606
- id
10607
- };
10608
- });
10609
- const buildCodeBlockDiffPayload = (id, index) => findGroupStep(historyService.getCodeBlockHistoryGroups(), id, index, ({ oldContent, newContent }) => {
10610
- if (!oldContent || !newContent) return null;
10611
- return {
10612
- category: "code-block",
10613
- lastValue: oldContent,
10614
- value: newContent,
10615
- currentValue: codeBlockService.getCodeContentById(id) || null,
10616
- targetLabel: newContent.name || oldContent.name || `${id}`,
10617
- id
10618
- };
10619
- });
11454
+ const buildPageDiffPayload = (index) => buildDiffPayload({
11455
+ category: "node",
11456
+ groups: () => historyService.getPageHistoryGroups(),
11457
+ getCurrent: (id) => editorService.getNodeById(id),
11458
+ resolveType: (n, o) => n.type || o.type || "",
11459
+ resolveLabel: (n, o) => n.name || o.name || n.type || o.type || ""
11460
+ }, index);
11461
+ const buildDataSourceDiffPayload = (id, index) => buildDiffPayload({
11462
+ category: "data-source",
11463
+ groups: () => historyService.getDataSourceHistoryGroups(),
11464
+ getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`),
11465
+ resolveType: (n, o) => n.type || o.type || "base",
11466
+ resolveLabel: (n, o, id) => n.title || o.title || `${id}`
11467
+ }, index, id);
11468
+ const buildCodeBlockDiffPayload = (id, index) => buildDiffPayload({
11469
+ category: "code-block",
11470
+ groups: () => historyService.getCodeBlockHistoryGroups(),
11471
+ getCurrent: (id) => codeBlockService.getCodeContentById(id),
11472
+ resolveLabel: (n, o, id) => n.name || o.name || `${id}`
11473
+ }, index, id);
10620
11474
  const onPageDiff = (index) => {
10621
11475
  const payload = buildPageDiffPayload(index);
10622
11476
  if (payload) diffDialogRef.value?.open(payload);
@@ -10629,169 +11483,259 @@
10629
11483
  const payload = buildCodeBlockDiffPayload(id, index);
10630
11484
  if (payload) diffDialogRef.value?.open(payload);
10631
11485
  };
10632
- const onConfirmRevert = (0, vue.shallowRef)();
10633
11486
  /**
10634
- * 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
11487
+ * 「回滚」统一入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
10635
11488
  * 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
10636
11489
  *
10637
- * 交互:先弹出该步骤的差异弹窗供用户确认,点击「确定回滚」后再真正执行回滚;
10638
- * 对没有可对比内容的步骤(如 add / remove / 多节点更新)则直接回滚。
11490
+ * 交互:
11491
+ * - 可差异对比的步骤(单节点 / 单实体 update):弹出差异弹窗供用户确认,点「确定回滚」再执行;
11492
+ * - 无法对比的步骤(add / remove / 多节点更新,payload 为 null):弹出普通二次确认框,确认后执行。
11493
+ *
11494
+ * 页面 / 数据源 / 代码块三类回滚仅「差异入参构造」与「实际 revert 调用」不同,
11495
+ * 由调用方分别传入 payload 与 revert,公共的弹窗 / 确认流程在此收敛。
11496
+ */
11497
+ const runRevert = (payload) => {
11498
+ if (payload && confirmDialogRef.value) return confirmDialogRef.value.confirm(payload);
11499
+ return confirmRevert();
11500
+ };
11501
+ /**
11502
+ * 回滚前置校验:若该历史步骤回滚所依赖的目标数据已被删除,则无法回滚。
11503
+ * - update(把旧值写回):被修改的目标必须仍存在;
11504
+ * - 页面 remove(还原被删节点):被删节点的原父容器必须仍存在,否则无处插回;
11505
+ * add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。
11506
+ *
11507
+ * 命中时弹出「回滚失败」提示并返回 true,调用方据此中止本次回滚。
10639
11508
  */
11509
+ const isPageRevertTargetMissing = (index) => {
11510
+ const step = historyService.getPageStepList()[index]?.step;
11511
+ if (!step) return false;
11512
+ if (step.opType === "update") return (step.diff ?? []).some((item) => {
11513
+ const id = item.newSchema?.id ?? item.oldSchema?.id;
11514
+ return id !== void 0 && !editorService.getNodeById(id, false);
11515
+ });
11516
+ if (step.opType === "remove") return (step.diff ?? []).some((item) => item.parentId !== void 0 && !editorService.getNodeById(item.parentId, false));
11517
+ return false;
11518
+ };
11519
+ /** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */
11520
+ const isDataSourceRevertTargetMissing = (id, index) => {
11521
+ const step = historyService.getDataSourceStepList(id)[index]?.step;
11522
+ return Boolean(step && step.opType === "update" && !dataSourceService.getDataSourceById(`${id}`));
11523
+ };
11524
+ /** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */
11525
+ const isCodeBlockRevertTargetMissing = (id, index) => {
11526
+ const step = historyService.getCodeBlockStepList(id)[index]?.step;
11527
+ return Boolean(step && step.opType === "update" && !codeBlockService.getCodeContentById(id));
11528
+ };
11529
+ /** 目标数据已被删除、无法回滚时的统一提示。 */
11530
+ const showRevertTargetMissing = () => {
11531
+ _tmagic_design.tMagicMessage.error("回滚失败:该记录对应的数据已被删除");
11532
+ };
10640
11533
  const onPageRevert = (index) => {
10641
- const payload = buildPageDiffPayload(index);
10642
- onConfirmRevert.value = () => editorService.revertPageStep(index);
10643
- if (payload) diffDialogRef.value?.open({ ...payload });
10644
- else onConfirmRevert.value();
11534
+ if (isPageRevertTargetMissing(index)) {
11535
+ showRevertTargetMissing();
11536
+ return Promise.resolve(null);
11537
+ }
11538
+ return runRevert(buildPageDiffPayload(index)).then((result) => result ? editorService.revertPageStep(index) : null);
10645
11539
  };
10646
11540
  const onDataSourceRevert = (id, index) => {
10647
- const payload = buildDataSourceDiffPayload(id, index);
10648
- onConfirmRevert.value = () => dataSourceService.revert(id, index);
10649
- if (payload) diffDialogRef.value?.open({ ...payload });
10650
- else onConfirmRevert.value();
11541
+ if (isDataSourceRevertTargetMissing(id, index)) {
11542
+ showRevertTargetMissing();
11543
+ return Promise.resolve(null);
11544
+ }
11545
+ return runRevert(buildDataSourceDiffPayload(id, index)).then((result) => result ? dataSourceService.revert(id, index) : null);
10651
11546
  };
10652
11547
  const onCodeBlockRevert = (id, index) => {
10653
- const payload = buildCodeBlockDiffPayload(id, index);
10654
- onConfirmRevert.value = () => codeBlockService.revert(id, index);
10655
- if (payload) diffDialogRef.value?.open({ ...payload });
10656
- else onConfirmRevert.value();
11548
+ if (isCodeBlockRevertTargetMissing(id, index)) {
11549
+ showRevertTargetMissing();
11550
+ return Promise.resolve(null);
11551
+ }
11552
+ return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) => result ? codeBlockService.revert(id, index) : null);
11553
+ };
11554
+ /**
11555
+ * 「回滚」二次确认:新增 / 删除 / 多节点更新等无法做差异对比的步骤,
11556
+ * 不弹差异弹窗,改用一个普通确认框替代「确定回滚」按钮,避免点击后无任何提示直接执行。
11557
+ * 用户取消时返回 false,调用方据此中止回滚。
11558
+ */
11559
+ const confirmRevert = () => confirmDialog("确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。");
11560
+ /**
11561
+ * 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
11562
+ * 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
11563
+ */
11564
+ const confirmDialog = async (message) => {
11565
+ try {
11566
+ await _tmagic_design.tMagicMessageBox.confirm(message, "提示", {
11567
+ confirmButtonText: "确定",
11568
+ cancelButtonText: "取消",
11569
+ type: "warning"
11570
+ });
11571
+ return true;
11572
+ } catch (e) {
11573
+ return false;
11574
+ }
11575
+ };
11576
+ /**
11577
+ * 把内存中(已清空对应类别后的)历史状态重新写回 IndexedDB,
11578
+ * 使本地持久化的那份与内存保持一致——即「连同本地保存的一并删除」。
11579
+ * 不支持 IndexedDB 或写入失败时静默忽略(内存清空已生效)。
11580
+ */
11581
+ const syncIndexedDB = async () => {
11582
+ try {
11583
+ await historyService.saveToIndexedDB();
11584
+ } catch (e) {}
11585
+ };
11586
+ const onPageClear = async () => {
11587
+ if (await confirmDialog("确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11588
+ historyService.clearPage();
11589
+ await syncIndexedDB();
11590
+ }
10657
11591
  };
10658
- const onDiffDialogClose = () => {
10659
- onConfirmRevert.value = void 0;
11592
+ const onDataSourceClear = async () => {
11593
+ if (await confirmDialog("确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11594
+ historyService.clearDataSource();
11595
+ await syncIndexedDB();
11596
+ }
11597
+ };
11598
+ const onCodeBlockClear = async () => {
11599
+ if (await confirmDialog("确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11600
+ historyService.clearCodeBlock();
11601
+ await syncIndexedDB();
11602
+ }
10660
11603
  };
10661
11604
  return (_ctx, _cache) => {
10662
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
10663
- "popper-class": "m-editor-history-list-popover",
10664
- placement: "bottom",
10665
- trigger: "click",
10666
- visible: visible.value,
10667
- width: 660
10668
- }, {
10669
- reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
10670
- effect: "dark",
11605
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [
11606
+ (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
11607
+ "popper-class": "m-editor-history-list-popover",
10671
11608
  placement: "bottom",
10672
- content: "历史记录"
11609
+ trigger: "click",
11610
+ visible: visible.value,
11611
+ "onUpdate:visible": _cache[3] || (_cache[3] = ($event) => visible.value = $event),
11612
+ width: 660
10673
11613
  }, {
10674
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
10675
- size: "small",
10676
- link: "",
10677
- onClick: _cache[2] || (_cache[2] = ($event) => visible.value = !visible.value)
11614
+ reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
11615
+ effect: "dark",
11616
+ placement: "bottom",
11617
+ content: "历史记录"
10678
11618
  }, {
10679
- icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(ClockIcon) }, null, 8, ["icon"])]),
11619
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
11620
+ size: "small",
11621
+ link: "",
11622
+ onClick: _cache[2] || (_cache[2] = ($event) => visible.value = !visible.value)
11623
+ }, {
11624
+ icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(ClockIcon) }, null, 8, ["icon"])]),
11625
+ _: 1
11626
+ })]),
10680
11627
  _: 1
10681
11628
  })]),
10682
- _: 1
10683
- })]),
10684
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$55, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
10685
- effect: "dark",
10686
- placement: "top",
10687
- content: "关闭"
10688
- }, {
10689
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
10690
- class: "m-editor-history-list-close",
10691
- size: "small",
10692
- link: "",
10693
- onClick: _cache[0] || (_cache[0] = ($event) => visible.value = false)
11629
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$55, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
11630
+ effect: "dark",
11631
+ placement: "top",
11632
+ content: "关闭"
10694
11633
  }, {
10695
- icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(CloseIcon) }, null, 8, ["icon"])]),
10696
- _: 1
10697
- })]),
10698
- _: 1
10699
- }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTabs), {
10700
- modelValue: activeTab.value,
10701
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => activeTab.value = $event),
10702
- class: "m-editor-history-list-tabs"
10703
- }, {
10704
- default: (0, vue.withCtx)(() => [
10705
- ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.guardReactiveProps)((0, vue.unref)(tabPaneComponent)?.props({
10706
- name: "page",
10707
- label: `页面 (${(0, vue.unref)(pageGroups).length})`
10708
- }) || {})), {
10709
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(PageTab_default, {
10710
- list: (0, vue.unref)(pageGroupsDisplay),
10711
- expanded: (0, vue.unref)(expanded),
10712
- onToggle: (0, vue.unref)(toggleGroup),
10713
- onGoto: onPageGoto,
10714
- onGotoInitial: onPageGotoInitial,
10715
- onDiffStep: onPageDiff,
10716
- onRevertStep: onPageRevert
10717
- }, null, 8, [
10718
- "list",
10719
- "expanded",
10720
- "onToggle"
10721
- ])]),
10722
- _: 1
10723
- }, 16)),
10724
- !disabledDataSource.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.mergeProps)({ key: 0 }, (0, vue.unref)(tabPaneComponent)?.props({
10725
- name: "data-source",
10726
- label: `数据源 (${(0, vue.unref)(dataSourceGroups).length})`
10727
- }) || {})), {
10728
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
10729
- title: "数据源",
10730
- prefix: "ds",
10731
- buckets: (0, vue.unref)(dataSourceGroupsByTarget),
10732
- expanded: (0, vue.unref)(expanded),
10733
- "describe-group": (0, vue.unref)(describeDataSourceGroup),
10734
- "describe-step": (0, vue.unref)(describeDataSourceStep),
10735
- "is-step-diffable": isDataSourceStepDiffable,
10736
- onToggle: (0, vue.unref)(toggleGroup),
10737
- onGoto: onDataSourceGoto,
10738
- onGotoInitial: onDataSourceGotoInitial,
10739
- onDiffStep: onDataSourceDiff,
10740
- onRevertStep: onDataSourceRevert
10741
- }, null, 8, [
10742
- "buckets",
10743
- "expanded",
10744
- "describe-group",
10745
- "describe-step",
10746
- "onToggle"
10747
- ])]),
10748
- _: 1
10749
- }, 16)) : (0, vue.createCommentVNode)("v-if", true),
10750
- !disabledCodeBlock.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.mergeProps)({ key: 1 }, (0, vue.unref)(tabPaneComponent)?.props({
10751
- name: "code-block",
10752
- label: `代码块 (${(0, vue.unref)(codeBlockGroups).length})`
10753
- }) || {})), {
10754
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
10755
- title: "代码块",
10756
- prefix: "cb",
10757
- buckets: (0, vue.unref)(codeBlockGroupsByTarget),
10758
- expanded: (0, vue.unref)(expanded),
10759
- "describe-group": (0, vue.unref)(describeCodeBlockGroup),
10760
- "describe-step": (0, vue.unref)(describeCodeBlockStep),
10761
- "is-step-diffable": isCodeBlockStepDiffable,
10762
- onToggle: (0, vue.unref)(toggleGroup),
10763
- onGoto: onCodeBlockGoto,
10764
- onGotoInitial: onCodeBlockGotoInitial,
10765
- onDiffStep: onCodeBlockDiff,
10766
- onRevertStep: onCodeBlockRevert
10767
- }, null, 8, [
10768
- "buckets",
10769
- "expanded",
10770
- "describe-group",
10771
- "describe-step",
10772
- "onToggle"
10773
- ])]),
11634
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
11635
+ class: "m-editor-history-list-close",
11636
+ size: "small",
11637
+ link: "",
11638
+ onClick: _cache[0] || (_cache[0] = ($event) => visible.value = false)
11639
+ }, {
11640
+ icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(CloseIcon) }, null, 8, ["icon"])]),
10774
11641
  _: 1
10775
- }, 16)) : (0, vue.createCommentVNode)("v-if", true),
10776
- ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)((0, vue.unref)(extraTabs), (tab) => {
10777
- return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.mergeProps)({ key: tab.name }, { ref_for: true }, (0, vue.unref)(tabPaneComponent)?.props({
10778
- name: tab.name,
10779
- label: resolveTabLabel(tab)
10780
- }) || {}), {
10781
- default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tab.component), (0, vue.mergeProps)({ ref_for: true }, tab.props || {}, (0, vue.toHandlers)(tab.listeners || {})), null, 16))]),
10782
- _: 2
10783
- }, 1040);
10784
- }), 128))
10785
- ]),
11642
+ })]),
11643
+ _: 1
11644
+ }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTabs), {
11645
+ modelValue: activeTab.value,
11646
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => activeTab.value = $event),
11647
+ class: "m-editor-history-list-tabs"
11648
+ }, {
11649
+ default: (0, vue.withCtx)(() => [
11650
+ ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.guardReactiveProps)((0, vue.unref)(tabPaneComponent)?.props({
11651
+ name: "page",
11652
+ label: `页面 (${(0, vue.unref)(pageGroups).length})`
11653
+ }) || {})), {
11654
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(PageTab_default, {
11655
+ list: (0, vue.unref)(pageGroupsDisplay),
11656
+ expanded: (0, vue.unref)(expanded),
11657
+ marker: pageMarker.value,
11658
+ onToggle: (0, vue.unref)(toggleGroup),
11659
+ onGoto: onPageGoto,
11660
+ onGotoInitial: onPageGotoInitial,
11661
+ onDiffStep: onPageDiff,
11662
+ onRevertStep: onPageRevert,
11663
+ onSelect: onPageSelect,
11664
+ onClear: onPageClear
11665
+ }, null, 8, [
11666
+ "list",
11667
+ "expanded",
11668
+ "marker",
11669
+ "onToggle"
11670
+ ])]),
11671
+ _: 1
11672
+ }, 16)),
11673
+ !disabledDataSource.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.mergeProps)({ key: 0 }, (0, vue.unref)(tabPaneComponent)?.props({
11674
+ name: "data-source",
11675
+ label: `数据源 (${(0, vue.unref)(dataSourceGroups).length})`
11676
+ }) || {})), {
11677
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
11678
+ config: dataSourceConfig,
11679
+ buckets: (0, vue.unref)(dataSourceGroupsByTarget),
11680
+ expanded: (0, vue.unref)(expanded),
11681
+ onToggle: (0, vue.unref)(toggleGroup),
11682
+ onGoto: onDataSourceGoto,
11683
+ onGotoInitial: onDataSourceGotoInitial,
11684
+ onDiffStep: onDataSourceDiff,
11685
+ onRevertStep: onDataSourceRevert,
11686
+ onClear: onDataSourceClear
11687
+ }, null, 8, [
11688
+ "buckets",
11689
+ "expanded",
11690
+ "onToggle"
11691
+ ])]),
11692
+ _: 1
11693
+ }, 16)) : (0, vue.createCommentVNode)("v-if", true),
11694
+ !disabledCodeBlock.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.normalizeProps)((0, vue.mergeProps)({ key: 1 }, (0, vue.unref)(tabPaneComponent)?.props({
11695
+ name: "code-block",
11696
+ label: `代码块 (${(0, vue.unref)(codeBlockGroups).length})`
11697
+ }) || {})), {
11698
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
11699
+ config: codeBlockConfig,
11700
+ buckets: (0, vue.unref)(codeBlockGroupsByTarget),
11701
+ expanded: (0, vue.unref)(expanded),
11702
+ onToggle: (0, vue.unref)(toggleGroup),
11703
+ onGoto: onCodeBlockGoto,
11704
+ onGotoInitial: onCodeBlockGotoInitial,
11705
+ onDiffStep: onCodeBlockDiff,
11706
+ onRevertStep: onCodeBlockRevert,
11707
+ onClear: onCodeBlockClear
11708
+ }, null, 8, [
11709
+ "buckets",
11710
+ "expanded",
11711
+ "onToggle"
11712
+ ])]),
11713
+ _: 1
11714
+ }, 16)) : (0, vue.createCommentVNode)("v-if", true),
11715
+ ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)((0, vue.unref)(extraTabs), (tab) => {
11716
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(tabPaneComponent)?.component || "el-tab-pane"), (0, vue.mergeProps)({ key: tab.name }, { ref_for: true }, (0, vue.unref)(tabPaneComponent)?.props({
11717
+ name: tab.name,
11718
+ label: resolveTabLabel(tab)
11719
+ }) || {}), {
11720
+ default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tab.component), (0, vue.mergeProps)({ ref_for: true }, tab.props || {}, (0, vue.toHandlers)(tab.listeners || {})), null, 16))]),
11721
+ _: 2
11722
+ }, 1040);
11723
+ }), 128))
11724
+ ]),
11725
+ _: 1
11726
+ }, 8, ["modelValue"])])]),
10786
11727
  _: 1
10787
- }, 8, ["modelValue"])])]),
10788
- _: 1
10789
- }, 8, ["visible"]), (0, vue.createVNode)(HistoryDiffDialog_default, {
10790
- ref: "diffDialog",
10791
- "extend-state": (0, vue.unref)(extendFormState),
10792
- "on-confirm": onConfirmRevert.value,
10793
- onClose: onDiffDialogClose
10794
- }, null, 8, ["extend-state", "on-confirm"])], 64);
11728
+ }, 8, ["visible"]),
11729
+ (0, vue.createVNode)(HistoryDiffDialog_default, {
11730
+ ref: "diffDialog",
11731
+ "extend-state": (0, vue.unref)(extendFormState)
11732
+ }, null, 8, ["extend-state"]),
11733
+ (0, vue.createVNode)(HistoryDiffDialog_default, {
11734
+ ref: "confirmDialog",
11735
+ "is-confirm": true,
11736
+ "extend-state": (0, vue.unref)(extendFormState)
11737
+ }, null, 8, ["extend-state"])
11738
+ ], 64);
10795
11739
  };
10796
11740
  }
10797
11741
  });
@@ -11038,7 +11982,7 @@
11038
11982
  disabled: () => editorService.get("node")?.type === _tmagic_core.NodeType.PAGE,
11039
11983
  handler: () => {
11040
11984
  const node = editorService.get("node");
11041
- node && editorService.remove(node);
11985
+ node && editorService.remove(node, { historySource: "toolbar" });
11042
11986
  }
11043
11987
  });
11044
11988
  break;
@@ -11402,7 +12346,11 @@
11402
12346
  if (record.propPath?.startsWith("style") && record.value === "") (0, _tmagic_utils.setValueByKeyPath)(record.propPath, record.value, newValue);
11403
12347
  });
11404
12348
  }
11405
- editorService.update(newValue, { changeRecords: eventData?.changeRecords });
12349
+ const historySource = eventData ? "props" : "code";
12350
+ editorService.update(newValue, {
12351
+ changeRecords: eventData?.changeRecords,
12352
+ historySource
12353
+ });
11406
12354
  } catch (e) {
11407
12355
  emit("submit-error", e);
11408
12356
  }
@@ -12371,7 +13319,7 @@
12371
13319
  const editCode = (id) => {
12372
13320
  emit("edit", id);
12373
13321
  };
12374
- const deleteCode = async (id) => {
13322
+ const deleteCode = async (id, { historySource } = {}) => {
12375
13323
  const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
12376
13324
  const existBinds = Boolean(currentCode?.items?.length);
12377
13325
  const undeleteableList = codeBlockService.getUndeletableList() || [];
@@ -12381,7 +13329,7 @@
12381
13329
  cancelButtonText: "取消",
12382
13330
  type: "warning"
12383
13331
  });
12384
- emit("remove", id);
13332
+ emit("remove", id, { historySource });
12385
13333
  } else if (typeof props.customError === "function") props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
12386
13334
  else if (existBinds) _tmagic_design.tMagicMessage.error("代码块存在绑定关系,不可删除");
12387
13335
  else _tmagic_design.tMagicMessage.error("代码块不可删除");
@@ -12431,7 +13379,7 @@
12431
13379
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, {
12432
13380
  icon: (0, vue.unref)(_element_plus_icons_vue.Close),
12433
13381
  class: "edit-icon",
12434
- onClick: (0, vue.withModifiers)(($event) => deleteCode(`${data.key}`), ["stop"])
13382
+ onClick: (0, vue.withModifiers)(($event) => deleteCode(`${data.key}`, { historySource: "tree" }), ["stop"])
12435
13383
  }, null, 8, ["icon", "onClick"])]),
12436
13384
  _: 2
12437
13385
  }, 1024)) : (0, vue.createCommentVNode)("v-if", true),
@@ -12479,7 +13427,7 @@
12479
13427
  const codeBlock = codeBlockService.getCodeContentById(selectId);
12480
13428
  if (!codeBlock) return;
12481
13429
  const newCodeId = await codeBlockService.getUniqueId();
12482
- codeBlockService.setCodeDslById(newCodeId, cloneDeep$1(codeBlock));
13430
+ codeBlockService.setCodeDslById(newCodeId, cloneDeep$1(codeBlock), { historySource: "tree-contextmenu" });
12483
13431
  }
12484
13432
  },
12485
13433
  {
@@ -12541,7 +13489,7 @@
12541
13489
  if (codeBlockListRef.value) for (const [, status] of codeBlockListRef.value.nodeStatusMap.entries()) status.selected = false;
12542
13490
  };
12543
13491
  const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu$1((id) => {
12544
- codeBlockListRef.value?.deleteCode(id);
13492
+ codeBlockListRef.value?.deleteCode(id, { historySource: "tree-contextmenu" });
12545
13493
  });
12546
13494
  const menuData = (0, vue.computed)(() => props.customContentMenu(contentMenuData, "code-block"));
12547
13495
  return (_ctx, _cache) => {
@@ -12629,8 +13577,11 @@
12629
13577
  editDialog.value.show();
12630
13578
  };
12631
13579
  const submitDataSourceHandler = (value, eventData) => {
12632
- if (value.id) dataSourceService.update(value, { changeRecords: eventData.changeRecords });
12633
- else dataSourceService.add(value);
13580
+ if (value.id) dataSourceService.update(value, {
13581
+ changeRecords: eventData.changeRecords,
13582
+ historySource: "props"
13583
+ });
13584
+ else dataSourceService.add(value, { historySource: "props" });
12634
13585
  editDialog.value?.hide();
12635
13586
  };
12636
13587
  return {
@@ -12963,7 +13914,7 @@
12963
13914
  if (!selectId) return;
12964
13915
  const ds = dataSourceService.getDataSourceById(selectId);
12965
13916
  if (!ds) return;
12966
- dataSourceService.add(cloneDeep$1(ds));
13917
+ dataSourceService.add(cloneDeep$1(ds), { historySource: "tree-contextmenu" });
12967
13918
  }
12968
13919
  },
12969
13920
  {
@@ -13040,7 +13991,7 @@
13040
13991
  cancelButtonText: "取消",
13041
13992
  type: "warning"
13042
13993
  });
13043
- dataSourceService.remove(id);
13994
+ dataSourceService.remove(id, { historySource: "tree-contextmenu" });
13044
13995
  };
13045
13996
  const dataSourceListRef = (0, vue.useTemplateRef)("dataSourceList");
13046
13997
  const filterTextChangeHandler = (val) => {
@@ -13147,7 +14098,12 @@
13147
14098
  var FolderMinusIcon_default = FolderMinusIcon_vue_vue_type_script_setup_true_lang_default;
13148
14099
  //#endregion
13149
14100
  //#region packages/editor/src/utils/content-menu.ts
13150
- var useDeleteMenu = () => ({
14101
+ /**
14102
+ * 共享的右键菜单项构造器(画布 ViewerMenu 与图层树 LayerMenu 共用)。
14103
+ * `historySource` 用于标记本次操作的途径,调用方按所在面板传入:
14104
+ * 画布传 `'stage-contextmenu'`,树形面板传 `'tree-contextmenu'`。
14105
+ */
14106
+ var useDeleteMenu = (historySource) => ({
13151
14107
  type: "button",
13152
14108
  text: "删除",
13153
14109
  icon: _element_plus_icons_vue.Delete,
@@ -13157,7 +14113,7 @@
13157
14113
  },
13158
14114
  handler: ({ editorService }) => {
13159
14115
  const nodes = editorService.get("nodes");
13160
- nodes && editorService.remove(nodes);
14116
+ nodes && editorService.remove(nodes, { historySource });
13161
14117
  }
13162
14118
  });
13163
14119
  var useCopyMenu = () => ({
@@ -13169,7 +14125,7 @@
13169
14125
  nodes && editorService?.copy(nodes);
13170
14126
  }
13171
14127
  });
13172
- var usePasteMenu = (menu) => ({
14128
+ var usePasteMenu = (historySource, menu) => ({
13173
14129
  type: "button",
13174
14130
  text: "粘贴",
13175
14131
  icon: (0, vue.markRaw)(_element_plus_icons_vue.DocumentCopy),
@@ -13186,17 +14142,20 @@
13186
14142
  editorService.paste({
13187
14143
  left: initialLeft,
13188
14144
  top: initialTop
13189
- });
13190
- } else editorService.paste();
14145
+ }, void 0, { historySource });
14146
+ } else editorService.paste(void 0, void 0, { historySource });
13191
14147
  }
13192
14148
  });
13193
- var moveTo = async (id, { editorService }) => {
14149
+ var moveTo = async (id, { editorService }, historySource) => {
13194
14150
  const nodes = editorService.get("nodes") || [];
13195
14151
  const parent = editorService.getNodeById(id);
13196
14152
  if (!parent || nodes.length === 0) return;
13197
- await editorService.moveToContainer((0, _tmagic_core.cloneDeep)(nodes), parent.id, { doNotSwitchPage: true });
14153
+ await editorService.moveToContainer((0, _tmagic_core.cloneDeep)(nodes), parent.id, {
14154
+ doNotSwitchPage: true,
14155
+ historySource
14156
+ });
13198
14157
  };
13199
- var useMoveToMenu = ({ editorService }) => {
14158
+ var useMoveToMenu = ({ editorService }, historySource) => {
13200
14159
  return {
13201
14160
  type: "button",
13202
14161
  text: "移动至",
@@ -13209,7 +14168,7 @@
13209
14168
  text: `${page.name}(${page.id})`,
13210
14169
  type: "button",
13211
14170
  handler: (services) => {
13212
- moveTo(page.id, services);
14171
+ moveTo(page.id, services, historySource);
13213
14172
  }
13214
14173
  }))
13215
14174
  };
@@ -13242,7 +14201,7 @@
13242
14201
  name: component.text,
13243
14202
  type: component.type,
13244
14203
  ...component.data || {}
13245
- });
14204
+ }, void 0, { historySource: "tree-contextmenu" });
13246
14205
  }
13247
14206
  }));
13248
14207
  const getSubMenuData = (0, vue.computed)(() => {
@@ -13251,7 +14210,7 @@
13251
14210
  type: "button",
13252
14211
  icon: _element_plus_icons_vue.Files,
13253
14212
  handler: () => {
13254
- editorService.add({ type: "tab-pane" });
14213
+ editorService.add({ type: "tab-pane" }, void 0, { historySource: "tree-contextmenu" });
13255
14214
  }
13256
14215
  }];
13257
14216
  if (node.value?.items) return componentList.value.reduce((subMenuData, group, index) => subMenuData.concat(createMenuItems(group), index < componentList.value.length - 1 ? [{
@@ -13278,9 +14237,9 @@
13278
14237
  items: getSubMenuData.value
13279
14238
  },
13280
14239
  useCopyMenu(),
13281
- usePasteMenu(),
13282
- useDeleteMenu(),
13283
- useMoveToMenu(services),
14240
+ usePasteMenu("tree-contextmenu"),
14241
+ useDeleteMenu("tree-contextmenu"),
14242
+ useMoveToMenu(services, "tree-contextmenu"),
13284
14243
  ...props.layerContentMenu
13285
14244
  ], "layer"));
13286
14245
  const show = (e) => {
@@ -13311,7 +14270,7 @@
13311
14270
  editorService.update({
13312
14271
  id: props.data.id,
13313
14272
  visible
13314
- });
14273
+ }, { historySource: "tree" });
13315
14274
  };
13316
14275
  return (_ctx, _cache) => {
13317
14276
  return __props.data.type !== "page" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
@@ -13927,7 +14886,7 @@
13927
14886
  name: text,
13928
14887
  type,
13929
14888
  ...data
13930
- });
14889
+ }, void 0, { historySource: "component-panel" });
13931
14890
  };
13932
14891
  const dragstartHandler = ({ text, type, data = {} }, e) => {
13933
14892
  e.dataTransfer?.setData("text/json", (0, serialize_javascript.default)({
@@ -14072,7 +15031,11 @@
14072
15031
  });
14073
15032
  }
14074
15033
  }, { immediate: true });
14075
- const activeTabName = (0, vue.ref)(props.data?.status);
15034
+ const activeTabName = (0, vue.computed)({
15035
+ get: () => uiService.get("sideBarActiveTabName"),
15036
+ set: (value) => uiService.set("sideBarActiveTabName", value)
15037
+ });
15038
+ uiService.set("sideBarActiveTabName", props.data?.status || "");
14076
15039
  const getItemConfig = (data) => {
14077
15040
  const map = {
14078
15041
  [SideItemKey.COMPONENT_LIST]: {
@@ -14732,11 +15695,11 @@
14732
15695
  display: () => canCenter.value,
14733
15696
  handler: () => {
14734
15697
  if (!nodes.value) return;
14735
- editorService.alignCenter(nodes.value);
15698
+ editorService.alignCenter(nodes.value, { historySource: "stage-contextmenu" });
14736
15699
  }
14737
15700
  },
14738
15701
  useCopyMenu(),
14739
- usePasteMenu(menuRef),
15702
+ usePasteMenu("stage-contextmenu", menuRef),
14740
15703
  {
14741
15704
  type: "divider",
14742
15705
  direction: "horizontal",
@@ -14751,7 +15714,7 @@
14751
15714
  icon: (0, vue.markRaw)(_element_plus_icons_vue.Top),
14752
15715
  display: () => !(0, _tmagic_utils.isPage)(node.value) && !(0, _tmagic_utils.isPageFragment)(node.value) && !props.isMultiSelect,
14753
15716
  handler: () => {
14754
- editorService.moveLayer(1);
15717
+ editorService.moveLayer(1, { historySource: "stage-contextmenu" });
14755
15718
  }
14756
15719
  },
14757
15720
  {
@@ -14760,7 +15723,7 @@
14760
15723
  icon: (0, vue.markRaw)(_element_plus_icons_vue.Bottom),
14761
15724
  display: () => !(0, _tmagic_utils.isPage)(node.value) && !(0, _tmagic_utils.isPageFragment)(node.value) && !props.isMultiSelect,
14762
15725
  handler: () => {
14763
- editorService.moveLayer(-1);
15726
+ editorService.moveLayer(-1, { historySource: "stage-contextmenu" });
14764
15727
  }
14765
15728
  },
14766
15729
  {
@@ -14769,7 +15732,7 @@
14769
15732
  icon: (0, vue.markRaw)(_element_plus_icons_vue.Top),
14770
15733
  display: () => !(0, _tmagic_utils.isPage)(node.value) && !(0, _tmagic_utils.isPageFragment)(node.value) && !props.isMultiSelect,
14771
15734
  handler: () => {
14772
- editorService.moveLayer(LayerOffset.TOP);
15735
+ editorService.moveLayer(LayerOffset.TOP, { historySource: "stage-contextmenu" });
14773
15736
  }
14774
15737
  },
14775
15738
  {
@@ -14778,16 +15741,16 @@
14778
15741
  icon: (0, vue.markRaw)(_element_plus_icons_vue.Bottom),
14779
15742
  display: () => !(0, _tmagic_utils.isPage)(node.value) && !(0, _tmagic_utils.isPageFragment)(node.value) && !props.isMultiSelect,
14780
15743
  handler: () => {
14781
- editorService.moveLayer(LayerOffset.BOTTOM);
15744
+ editorService.moveLayer(LayerOffset.BOTTOM, { historySource: "stage-contextmenu" });
14782
15745
  }
14783
15746
  },
14784
- useMoveToMenu(services),
15747
+ useMoveToMenu(services, "stage-contextmenu"),
14785
15748
  {
14786
15749
  type: "divider",
14787
15750
  direction: "horizontal",
14788
15751
  display: () => !(0, _tmagic_utils.isPage)(node.value) && !(0, _tmagic_utils.isPageFragment)(node.value) && !props.isMultiSelect
14789
15752
  },
14790
- useDeleteMenu(),
15753
+ useDeleteMenu("stage-contextmenu"),
14791
15754
  {
14792
15755
  type: "divider",
14793
15756
  direction: "horizontal"
@@ -15044,7 +16007,7 @@
15044
16007
  left: (0, _tmagic_utils.calcValueByFontsize)(doc, left / zoom.value)
15045
16008
  };
15046
16009
  config.data.inputEvent = e;
15047
- editorService.add(config.data, parent);
16010
+ editorService.add(config.data, parent, { historySource: "component-panel" });
15048
16011
  }
15049
16012
  };
15050
16013
  return (_ctx, _cache) => {
@@ -15266,17 +16229,6 @@
15266
16229
  ],
15267
16230
  sync: ["setCodeDslByIdSync"]
15268
16231
  };
15269
- /**
15270
- * 「回滚」生成的新 step 简短描述。仅 service 层使用。
15271
- */
15272
- var describeRevertCodeBlockStep = (step) => {
15273
- const { oldContent, newContent, changeRecords, id } = step;
15274
- if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
15275
- if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
15276
- const name = newContent?.name || oldContent?.name || `${id}`;
15277
- const propPath = changeRecords?.[0]?.propPath;
15278
- return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
15279
- };
15280
16232
  var CodeBlock = class extends BaseService {
15281
16233
  state = (0, vue.reactive)({
15282
16234
  codeDsl: null,
@@ -15285,6 +16237,16 @@
15285
16237
  undeletableList: [],
15286
16238
  paramsColConfig: void 0
15287
16239
  });
16240
+ /**
16241
+ * 最近一次写入历史栈的代码块历史记录 uuid(单条写入场景:新增 / 更新)。
16242
+ * 供 setCodeDslById(Sync)AndGetHistoryId 取回,普通方法不读取它。
16243
+ */
16244
+ lastPushedHistoryId = null;
16245
+ /**
16246
+ * deleteCodeDslByIds 一次删除多个代码块时,按写入顺序收集的历史记录 uuid 列表。
16247
+ * 在 deleteCodeDslByIds 入口处重置,供 deleteCodeDslByIdsAndGetHistoryId 取回。
16248
+ */
16249
+ lastDeletedHistoryIds = [];
15288
16250
  constructor() {
15289
16251
  super([...canUsePluginMethods$4.async.map((methodName) => ({
15290
16252
  name: methodName,
@@ -15332,10 +16294,12 @@
15332
16294
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
15333
16295
  * @returns {void}
15334
16296
  */
15335
- async setCodeDslById(id, codeConfig, { changeRecords, doNotPushHistory = false } = {}) {
16297
+ async setCodeDslById(id, codeConfig, { changeRecords, doNotPushHistory = false, historyDescription, historySource } = {}) {
15336
16298
  this.setCodeDslByIdSync(id, codeConfig, true, {
15337
16299
  changeRecords,
15338
- doNotPushHistory
16300
+ doNotPushHistory,
16301
+ historyDescription,
16302
+ historySource
15339
16303
  });
15340
16304
  }
15341
16305
  /**
@@ -15350,7 +16314,7 @@
15350
16314
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
15351
16315
  * @returns {void}
15352
16316
  */
15353
- setCodeDslByIdSync(id, codeConfig, force = true, { changeRecords, doNotPushHistory = false, historyDescription } = {}) {
16317
+ setCodeDslByIdSync(id, codeConfig, force = true, { changeRecords, doNotPushHistory = false, historyDescription, historySource } = {}) {
15354
16318
  const codeDsl = this.getCodeDsl();
15355
16319
  if (!codeDsl) throw new Error("dsl中没有codeBlocks");
15356
16320
  if (codeDsl[id] && !force) return;
@@ -15365,12 +16329,13 @@
15365
16329
  ...codeConfigProcessed
15366
16330
  };
15367
16331
  const newContent = cloneDeep$1(codeDsl[id]);
15368
- if (!doNotPushHistory) history_default.pushCodeBlock(id, {
16332
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushCodeBlock(id, {
15369
16333
  oldContent,
15370
16334
  newContent,
15371
16335
  changeRecords,
15372
- historyDescription
15373
- });
16336
+ historyDescription,
16337
+ source: historySource
16338
+ })?.uuid ?? null;
15374
16339
  this.emit("addOrUpdate", id, codeDsl[id]);
15375
16340
  }
15376
16341
  /**
@@ -15450,20 +16415,53 @@
15450
16415
  * @param options 可选配置
15451
16416
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
15452
16417
  */
15453
- async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription } = {}) {
16418
+ async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription, historySource } = {}) {
15454
16419
  const currentDsl = await this.getCodeDsl();
15455
16420
  if (!currentDsl) return;
16421
+ this.lastDeletedHistoryIds = [];
15456
16422
  codeIds.forEach((id) => {
15457
16423
  const oldContent = currentDsl[id] ? cloneDeep$1(currentDsl[id]) : null;
15458
16424
  delete currentDsl[id];
15459
- if (oldContent && !doNotPushHistory) history_default.pushCodeBlock(id, {
15460
- oldContent,
15461
- newContent: null,
15462
- historyDescription
15463
- });
16425
+ if (oldContent && !doNotPushHistory) {
16426
+ const uuid = history_default.pushCodeBlock(id, {
16427
+ oldContent,
16428
+ newContent: null,
16429
+ historyDescription,
16430
+ source: historySource
16431
+ })?.uuid;
16432
+ if (uuid) this.lastDeletedHistoryIds.push(uuid);
16433
+ }
15464
16434
  this.emit("remove", id);
15465
16435
  });
15466
16436
  }
16437
+ /**
16438
+ * 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
16439
+ * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link CodeBlockStepValue.uuid}),
16440
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16441
+ *
16442
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时:单条写入返回 null,批量删除返回空数组。
16443
+ */
16444
+ /** 等价于 {@link setCodeDslById},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16445
+ async setCodeDslByIdAndGetHistoryId(id, codeConfig, options = {}) {
16446
+ this.lastPushedHistoryId = null;
16447
+ await this.setCodeDslById(id, codeConfig, options);
16448
+ return this.lastPushedHistoryId;
16449
+ }
16450
+ /** 等价于 {@link setCodeDslByIdSync},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16451
+ setCodeDslByIdSyncAndGetHistoryId(id, codeConfig, force = true, options = {}) {
16452
+ this.lastPushedHistoryId = null;
16453
+ this.setCodeDslByIdSync(id, codeConfig, force, options);
16454
+ return this.lastPushedHistoryId;
16455
+ }
16456
+ /**
16457
+ * 等价于 {@link deleteCodeDslByIds},但返回本次写入的全部历史记录 uuid(按删除顺序)。
16458
+ * 一次删除多个代码块会产生多条历史记录,因此返回数组;未写入任何历史时返回空数组。
16459
+ */
16460
+ async deleteCodeDslByIdsAndGetHistoryId(codeIds, options = {}) {
16461
+ this.lastDeletedHistoryIds = [];
16462
+ await this.deleteCodeDslByIds(codeIds, options);
16463
+ return [...this.lastDeletedHistoryIds];
16464
+ }
15467
16465
  setParamsColConfig(config) {
15468
16466
  this.state.paramsColConfig = config;
15469
16467
  }
@@ -15539,10 +16537,25 @@
15539
16537
  async revert(id, index) {
15540
16538
  const entry = history_default.getCodeBlockStepList(id)[index];
15541
16539
  if (!entry?.applied) return null;
15542
- const description = `回滚 #${index + 1}: ${describeRevertCodeBlockStep(entry.step)}`;
16540
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
16541
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
16542
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.name)}`;
15543
16543
  return await this.applyRevertStep(entry.step, description);
15544
16544
  }
15545
16545
  /**
16546
+ * 通过历史记录 uuid 回滚某条代码块历史步骤,语义同 {@link revert},
16547
+ * 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid({@link CodeBlockStepValue.uuid})
16548
+ * 在全部代码块栈中定位对应步骤后再回滚。
16549
+ *
16550
+ * @param uuid 目标历史记录的 uuid,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回
16551
+ * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
16552
+ */
16553
+ async revertById(uuid) {
16554
+ const location = history_default.findCodeBlockStepLocationByUuid(uuid);
16555
+ if (!location) return null;
16556
+ return await this.revert(location.id, location.index);
16557
+ }
16558
+ /**
15546
16559
  * 生成代码块唯一id
15547
16560
  * @returns {Id} 代码块唯一id
15548
16561
  */
@@ -15607,16 +16620,23 @@
15607
16620
  * 差异仅在于通过公开的 setCodeDslByIdSync / deleteCodeDslByIds 触发 push。
15608
16621
  */
15609
16622
  async applyRevertStep(step, historyDescription) {
15610
- const { id, oldContent, newContent, changeRecords } = step;
15611
- if (oldContent === null && newContent) {
15612
- await this.deleteCodeDslByIds([id], { historyDescription });
16623
+ const { id } = step;
16624
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
16625
+ if (!oldSchema && newSchema) {
16626
+ await this.deleteCodeDslByIds([id], {
16627
+ historyDescription,
16628
+ historySource: "rollback"
16629
+ });
15613
16630
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15614
16631
  }
15615
- if (oldContent && newContent === null) {
15616
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
16632
+ if (oldSchema && !newSchema) {
16633
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
16634
+ historyDescription,
16635
+ historySource: "rollback"
16636
+ });
15617
16637
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15618
16638
  }
15619
- if (!oldContent || !newContent) return null;
16639
+ if (!oldSchema || !newSchema) return null;
15620
16640
  if (changeRecords?.length) {
15621
16641
  const current = this.getCodeContentById(id);
15622
16642
  if (!current) return null;
@@ -15627,16 +16647,20 @@
15627
16647
  fallbackToFullReplace = true;
15628
16648
  break;
15629
16649
  }
15630
- const value = cloneDeep$1((0, _tmagic_utils.getValueByKeyPath)(record.propPath, oldContent));
16650
+ const value = cloneDeep$1((0, _tmagic_utils.getValueByKeyPath)(record.propPath, oldSchema));
15631
16651
  (0, _tmagic_utils.setValueByKeyPath)(record.propPath, value, patched);
15632
16652
  }
15633
- this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldContent) : patched, true, {
16653
+ this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, true, {
15634
16654
  changeRecords,
15635
- historyDescription
16655
+ historyDescription,
16656
+ historySource: "rollback"
15636
16657
  });
15637
16658
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15638
16659
  }
15639
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
16660
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
16661
+ historyDescription,
16662
+ historySource: "rollback"
16663
+ });
15640
16664
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15641
16665
  }
15642
16666
  /**
@@ -15654,19 +16678,20 @@
15654
16678
  * @param reverse true=撤销,false=重做
15655
16679
  */
15656
16680
  async applyHistoryStep(step, reverse) {
15657
- const { id, oldContent, newContent, changeRecords } = step;
15658
- if (oldContent === null && newContent) {
16681
+ const { id } = step;
16682
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
16683
+ if (!oldSchema && newSchema) {
15659
16684
  if (reverse) await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
15660
- else this.setCodeDslByIdSync(id, cloneDeep$1(newContent), true, { doNotPushHistory: true });
16685
+ else this.setCodeDslByIdSync(id, cloneDeep$1(newSchema), true, { doNotPushHistory: true });
15661
16686
  return;
15662
16687
  }
15663
- if (oldContent && newContent === null) {
15664
- if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { doNotPushHistory: true });
16688
+ if (oldSchema && !newSchema) {
16689
+ if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, { doNotPushHistory: true });
15665
16690
  else await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
15666
16691
  return;
15667
16692
  }
15668
- if (!oldContent || !newContent) return;
15669
- const sourceForValues = reverse ? oldContent : newContent;
16693
+ if (!oldSchema || !newSchema) return;
16694
+ const sourceForValues = reverse ? oldSchema : newSchema;
15670
16695
  if (changeRecords?.length) {
15671
16696
  const current = this.getCodeContentById(id);
15672
16697
  if (!current) return;
@@ -15735,18 +16760,6 @@
15735
16760
  "createId"
15736
16761
  ]
15737
16762
  };
15738
- /**
15739
- * 「回滚」生成的新 step 简短描述。
15740
- * 仅在 service 层使用,避免依赖 UI 层 composables。
15741
- */
15742
- var describeRevertDataSourceStep = (step) => {
15743
- const { oldSchema, newSchema, changeRecords, id } = step;
15744
- if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
15745
- if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
15746
- const title = newSchema?.title || oldSchema?.title || `${id}`;
15747
- const propPath = changeRecords?.[0]?.propPath;
15748
- return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
15749
- };
15750
16763
  var DataSource = class extends BaseService {
15751
16764
  state = (0, vue.reactive)({
15752
16765
  datasourceTypeList: [],
@@ -15757,6 +16770,12 @@
15757
16770
  events: {},
15758
16771
  methods: {}
15759
16772
  });
16773
+ /**
16774
+ * 最近一次写入历史栈的数据源历史记录 uuid。
16775
+ * 供 *AndGetHistoryId 系列方法在调用 add / update / remove 后取回本次产生的历史记录 id;
16776
+ * 普通方法不读取它,调用前由 *AndGetHistoryId 重置为 null。
16777
+ */
16778
+ lastPushedHistoryId = null;
15760
16779
  constructor() {
15761
16780
  super(canUsePluginMethods$3.sync.map((methodName) => ({
15762
16781
  name: methodName,
@@ -15800,17 +16819,18 @@
15800
16819
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
15801
16820
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
15802
16821
  */
15803
- add(config, { doNotPushHistory = false, historyDescription } = {}) {
16822
+ add(config, { doNotPushHistory = false, historyDescription, historySource } = {}) {
15804
16823
  const newConfig = {
15805
16824
  ...config,
15806
16825
  id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId()
15807
16826
  };
15808
16827
  this.get("dataSources").push(newConfig);
15809
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
16828
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
15810
16829
  oldSchema: null,
15811
16830
  newSchema: newConfig,
15812
- historyDescription
15813
- });
16831
+ historyDescription,
16832
+ source: historySource
16833
+ })?.uuid ?? null;
15814
16834
  this.emit("add", newConfig);
15815
16835
  return newConfig;
15816
16836
  }
@@ -15822,18 +16842,19 @@
15822
16842
  * @param data.doNotPushHistory 是否不写入历史记录(默认 false)
15823
16843
  * @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
15824
16844
  */
15825
- update(config, { changeRecords = [], doNotPushHistory = false, historyDescription } = {}) {
16845
+ update(config, { changeRecords = [], doNotPushHistory = false, historyDescription, historySource } = {}) {
15826
16846
  const dataSources = this.get("dataSources");
15827
16847
  const index = dataSources.findIndex((ds) => ds.id === config.id);
15828
16848
  const oldConfig = dataSources[index];
15829
16849
  const newConfig = cloneDeep$1(config);
15830
16850
  dataSources[index] = newConfig;
15831
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
16851
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
15832
16852
  oldSchema: oldConfig ? cloneDeep$1(oldConfig) : null,
15833
16853
  newSchema: newConfig,
15834
16854
  changeRecords,
15835
- historyDescription
15836
- });
16855
+ historyDescription,
16856
+ source: historySource
16857
+ })?.uuid ?? null;
15837
16858
  this.emit("update", newConfig, {
15838
16859
  oldConfig,
15839
16860
  changeRecords
@@ -15847,19 +16868,45 @@
15847
16868
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
15848
16869
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
15849
16870
  */
15850
- remove(id, { doNotPushHistory = false, historyDescription } = {}) {
16871
+ remove(id, { doNotPushHistory = false, historyDescription, historySource } = {}) {
15851
16872
  const dataSources = this.get("dataSources");
15852
16873
  const index = dataSources.findIndex((ds) => ds.id === id);
15853
16874
  const oldConfig = index !== -1 ? dataSources[index] : null;
15854
16875
  dataSources.splice(index, 1);
15855
- if (oldConfig && !doNotPushHistory) history_default.pushDataSource(id, {
16876
+ if (oldConfig && !doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(id, {
15856
16877
  oldSchema: cloneDeep$1(oldConfig),
15857
16878
  newSchema: null,
15858
- historyDescription
15859
- });
16879
+ historyDescription,
16880
+ source: historySource
16881
+ })?.uuid ?? null;
15860
16882
  this.emit("remove", id);
15861
16883
  }
15862
16884
  /**
16885
+ * 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
16886
+ * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link DataSourceStepValue.uuid}),
16887
+ * 而非数据源配置。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16888
+ *
16889
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时返回 null。
16890
+ */
16891
+ /** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16892
+ addAndGetHistoryId(config, options = {}) {
16893
+ this.lastPushedHistoryId = null;
16894
+ this.add(config, options);
16895
+ return this.lastPushedHistoryId;
16896
+ }
16897
+ /** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16898
+ updateAndGetHistoryId(config, options = {}) {
16899
+ this.lastPushedHistoryId = null;
16900
+ this.update(config, options);
16901
+ return this.lastPushedHistoryId;
16902
+ }
16903
+ /** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16904
+ removeAndGetHistoryId(id, options = {}) {
16905
+ this.lastPushedHistoryId = null;
16906
+ this.remove(id, options);
16907
+ return this.lastPushedHistoryId;
16908
+ }
16909
+ /**
15863
16910
  * 撤销指定数据源的最近一次变更。
15864
16911
  *
15865
16912
  * 内部走 add / update / remove,因此会自动触发 dataSourceService 的事件,
@@ -15927,9 +16974,24 @@
15927
16974
  revert(id, index) {
15928
16975
  const entry = history_default.getDataSourceStepList(id)[index];
15929
16976
  if (!entry?.applied) return null;
15930
- const description = `回滚 #${index + 1}: ${describeRevertDataSourceStep(entry.step)}`;
16977
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
16978
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
16979
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.title)}`;
15931
16980
  return this.applyRevertStep(entry.step, description);
15932
16981
  }
16982
+ /**
16983
+ * 通过历史记录 uuid 回滚某条数据源历史步骤,语义同 {@link revert},
16984
+ * 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid({@link DataSourceStepValue.uuid})
16985
+ * 在全部数据源栈中定位对应步骤后再回滚。
16986
+ *
16987
+ * @param uuid 目标历史记录的 uuid,通常由 {@link addAndGetHistoryId} 等方法返回
16988
+ * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
16989
+ */
16990
+ revertById(uuid) {
16991
+ const location = history_default.findDataSourceStepLocationByUuid(uuid);
16992
+ if (!location) return null;
16993
+ return this.revert(location.id, location.index);
16994
+ }
15933
16995
  createId() {
15934
16996
  return `ds_${(0, _tmagic_utils.guid)()}`;
15935
16997
  }
@@ -15988,13 +17050,20 @@
15988
17050
  * 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
15989
17051
  */
15990
17052
  applyRevertStep(step, historyDescription) {
15991
- const { id, oldSchema, newSchema, changeRecords } = step;
15992
- if (oldSchema === null && newSchema) {
15993
- this.remove(`${id}`, { historyDescription });
17053
+ const { id } = step;
17054
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
17055
+ if (!oldSchema && newSchema) {
17056
+ this.remove(`${id}`, {
17057
+ historyDescription,
17058
+ historySource: "rollback"
17059
+ });
15994
17060
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
15995
17061
  }
15996
- if (oldSchema && newSchema === null) {
15997
- this.add(cloneDeep$1(oldSchema), { historyDescription });
17062
+ if (oldSchema && !newSchema) {
17063
+ this.add(cloneDeep$1(oldSchema), {
17064
+ historyDescription,
17065
+ historySource: "rollback"
17066
+ });
15998
17067
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
15999
17068
  }
16000
17069
  if (!oldSchema || !newSchema) return null;
@@ -16013,11 +17082,15 @@
16013
17082
  }
16014
17083
  this.update(fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, {
16015
17084
  changeRecords,
16016
- historyDescription
17085
+ historyDescription,
17086
+ historySource: "rollback"
16017
17087
  });
16018
17088
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
16019
17089
  }
16020
- this.update(cloneDeep$1(oldSchema), { historyDescription });
17090
+ this.update(cloneDeep$1(oldSchema), {
17091
+ historyDescription,
17092
+ historySource: "rollback"
17093
+ });
16021
17094
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
16022
17095
  }
16023
17096
  /**
@@ -16035,13 +17108,14 @@
16035
17108
  * @param reverse true=撤销,false=重做
16036
17109
  */
16037
17110
  applyHistoryStep(step, reverse) {
16038
- const { id, oldSchema, newSchema, changeRecords } = step;
16039
- if (oldSchema === null && newSchema) {
17111
+ const { id } = step;
17112
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
17113
+ if (!oldSchema && newSchema) {
16040
17114
  if (reverse) this.remove(`${id}`, { doNotPushHistory: true });
16041
17115
  else this.add(cloneDeep$1(newSchema), { doNotPushHistory: true });
16042
17116
  return;
16043
17117
  }
16044
- if (oldSchema && newSchema === null) {
17118
+ if (oldSchema && !newSchema) {
16045
17119
  if (reverse) this.add(cloneDeep$1(oldSchema), { doNotPushHistory: true });
16046
17120
  else this.remove(`${id}`, { doNotPushHistory: true });
16047
17121
  return;
@@ -16334,7 +17408,7 @@
16334
17408
  [KeyBindingCommand.DELETE_NODE]: () => {
16335
17409
  const nodes = editor_default.get("nodes");
16336
17410
  if (!nodes || (0, _tmagic_utils.isPage)(nodes[0]) || (0, _tmagic_utils.isPageFragment)(nodes[0])) return;
16337
- editor_default.remove(nodes);
17411
+ editor_default.remove(nodes, { historySource: "shortcut" });
16338
17412
  },
16339
17413
  [KeyBindingCommand.COPY_NODE]: () => {
16340
17414
  const nodes = editor_default.get("nodes");
@@ -16344,13 +17418,13 @@
16344
17418
  const nodes = editor_default.get("nodes");
16345
17419
  if (!nodes || (0, _tmagic_utils.isPage)(nodes[0]) || (0, _tmagic_utils.isPageFragment)(nodes[0])) return;
16346
17420
  editor_default.copy(nodes);
16347
- editor_default.remove(nodes);
17421
+ editor_default.remove(nodes, { historySource: "shortcut" });
16348
17422
  },
16349
17423
  [KeyBindingCommand.PASTE_NODE]: () => {
16350
17424
  editor_default.get("nodes") && editor_default.paste({
16351
17425
  offsetX: 10,
16352
17426
  offsetY: 10
16353
- });
17427
+ }, void 0, { historySource: "shortcut" });
16354
17428
  },
16355
17429
  [KeyBindingCommand.UNDO]: () => {
16356
17430
  editor_default.undo();
@@ -16692,7 +17766,7 @@
16692
17766
  //#region packages/editor/src/initService.ts
16693
17767
  var initServiceState = (props, { editorService, historyService, componentListService, propsService, eventsService, uiService, codeBlockService, keybindingService, dataSourceService, depService }) => {
16694
17768
  (0, vue.watch)(() => props.modelValue, (modelValue) => {
16695
- editorService.set("root", modelValue || null);
17769
+ editorService.set("root", modelValue || null, { historySource: "initial" });
16696
17770
  }, { immediate: true });
16697
17771
  (0, vue.watch)(() => props.disabledMultiSelect, (disabledMultiSelect) => {
16698
17772
  editorService.set("disabledMultiSelect", disabledMultiSelect || false);
@@ -21967,6 +23041,7 @@
21967
23041
  exports.H_GUIDE_LINE_STORAGE_KEY = H_GUIDE_LINE_STORAGE_KEY;
21968
23042
  exports.HistoryDiffDialog = HistoryDiffDialog_default;
21969
23043
  exports.HistoryListBucket = Bucket_default;
23044
+ exports.HistoryListBucketTab = BucketTab_default;
21970
23045
  exports.Icon = Icon_default;
21971
23046
  exports.IdleTask = IdleTask;
21972
23047
  exports.KeyBindingCommand = KeyBindingCommand;
@@ -22016,17 +23091,24 @@
22016
23091
  exports.classifyDragSources = classifyDragSources;
22017
23092
  exports.codeBlockService = codeBlock_default;
22018
23093
  exports.collectRelatedNodes = collectRelatedNodes;
23094
+ exports.createStackStep = createStackStep;
22019
23095
  exports.dataSourceService = dataSource_default;
22020
23096
  exports.debug = debug;
22021
23097
  exports.default = plugin_default;
22022
23098
  exports.defaultIsExpandable = defaultIsExpandable;
22023
23099
  exports.depService = dep_default;
23100
+ exports.describeRevertStep = describeRevertStep;
23101
+ exports.describeStepForRevert = describeStepForRevert;
23102
+ exports.deserializeStacks = deserializeStacks;
22024
23103
  Object.defineProperty(exports, "designPlugin", {
22025
23104
  enumerable: true,
22026
23105
  get: function() {
22027
23106
  return _tmagic_design.default;
22028
23107
  }
22029
23108
  });
23109
+ exports.detectPageTargetId = detectPageTargetId;
23110
+ exports.detectPageTargetName = detectPageTargetName;
23111
+ exports.detectStackOpType = detectStackOpType;
22030
23112
  exports.displayTabConfig = displayTabConfig;
22031
23113
  exports.editorNodeMergeCustomizer = editorNodeMergeCustomizer;
22032
23114
  exports.editorService = editor_default;
@@ -22057,21 +23139,31 @@
22057
23139
  exports.getGuideLineFromCache = getGuideLineFromCache;
22058
23140
  exports.getInitPositionStyle = getInitPositionStyle;
22059
23141
  exports.getNodeIndex = getNodeIndex;
23142
+ exports.getOrCreateStack = getOrCreateStack;
22060
23143
  exports.getPageFragmentList = getPageFragmentList;
22061
23144
  exports.getPageList = getPageList;
22062
23145
  exports.getPageNameList = getPageNameList;
22063
23146
  exports.getPositionInContainer = getPositionInContainer;
22064
23147
  exports.getRelativeStyle = getRelativeStyle;
22065
23148
  exports.historyService = history_default;
23149
+ exports.idbDelete = idbDelete;
23150
+ exports.idbGet = idbGet;
23151
+ exports.idbSet = idbSet;
22066
23152
  exports.info = info;
22067
23153
  exports.isIncludeDataSource = isIncludeDataSource;
23154
+ exports.isIndexedDBSupported = isIndexedDBSupported;
22068
23155
  exports.loadMonaco = monaco_editor_default;
22069
23156
  exports.log = log;
23157
+ exports.markStackSaved = markStackSaved;
23158
+ exports.mergePageSteps = mergePageSteps;
23159
+ exports.mergeStackSteps = mergeStackSteps;
22070
23160
  exports.moveItemsInContainer = moveItemsInContainer;
22071
23161
  exports.numberOptions = numberOptions;
23162
+ exports.openIndexedDB = openIndexedDB;
22072
23163
  exports.propsService = props_default;
22073
23164
  exports.resolveSelectedNode = resolveSelectedNode;
22074
23165
  exports.serializeConfig = serializeConfig;
23166
+ exports.serializeStacks = serializeStacks;
22075
23167
  exports.setChildrenLayout = setChildrenLayout;
22076
23168
  exports.setEditorConfig = setEditorConfig;
22077
23169
  exports.setLayout = setLayout;
@@ -22086,6 +23178,7 @@
22086
23178
  });
22087
23179
  exports.toggleFixedPosition = toggleFixedPosition;
22088
23180
  exports.uiService = ui_default;
23181
+ exports.undoFloor = undoFloor;
22089
23182
  exports.updateStatus = updateStatus;
22090
23183
  exports.useCodeBlockEdit = useCodeBlockEdit;
22091
23184
  exports.useEditorContentHeight = useEditorContentHeight;