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

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 (76) hide show
  1. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -1
  2. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
  3. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +4 -1
  4. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +8 -2
  5. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +25 -8
  6. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
  7. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +25 -12
  8. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +27 -5
  9. package/dist/es/index.js +6 -3
  10. package/dist/es/initService.js +1 -1
  11. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
  12. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -55
  13. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -39
  14. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +149 -103
  15. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +32 -8
  16. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +335 -213
  17. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +36 -7
  18. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -60
  19. package/dist/es/layouts/history-list/composables.js +73 -32
  20. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
  21. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
  22. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +16 -3
  23. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +23 -1
  24. package/dist/es/services/codeBlock.js +96 -37
  25. package/dist/es/services/dataSource.js +70 -26
  26. package/dist/es/services/editor.js +266 -104
  27. package/dist/es/services/history.js +271 -212
  28. package/dist/es/services/ui.js +3 -0
  29. package/dist/es/style.css +53 -7
  30. package/dist/es/utils/data-source/index.js +2 -0
  31. package/dist/es/utils/editor.js +103 -49
  32. package/dist/es/utils/history.js +232 -0
  33. package/dist/es/utils/indexed-db.js +86 -0
  34. package/dist/es/utils/undo-redo.js +60 -1
  35. package/dist/style.css +53 -7
  36. package/dist/tmagic-editor.umd.cjs +2064 -1000
  37. package/package.json +7 -7
  38. package/src/components/CompareForm.vue +3 -1
  39. package/src/components/ToolButton.vue +2 -2
  40. package/src/fields/CodeSelectCol.vue +7 -1
  41. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
  42. package/src/fields/DataSourceFields.vue +37 -8
  43. package/src/fields/DataSourceMethodSelect.vue +9 -4
  44. package/src/fields/DataSourceMethods.vue +42 -21
  45. package/src/fields/StyleSetter/components/Border.vue +15 -6
  46. package/src/index.ts +1 -0
  47. package/src/initService.ts +1 -1
  48. package/src/layouts/Framework.vue +1 -1
  49. package/src/layouts/history-list/Bucket.vue +34 -71
  50. package/src/layouts/history-list/BucketTab.vue +46 -54
  51. package/src/layouts/history-list/GroupRow.vue +146 -111
  52. package/src/layouts/history-list/HistoryDiffDialog.vue +94 -68
  53. package/src/layouts/history-list/HistoryListPanel.vue +298 -115
  54. package/src/layouts/history-list/InitialRow.vue +28 -9
  55. package/src/layouts/history-list/PageTab.vue +57 -51
  56. package/src/layouts/history-list/composables.ts +157 -36
  57. package/src/layouts/page-bar/PageBar.vue +4 -2
  58. package/src/layouts/sidebar/Sidebar.vue +6 -1
  59. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +30 -2
  60. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +26 -1
  61. package/src/services/codeBlock.ts +113 -37
  62. package/src/services/dataSource.ts +94 -40
  63. package/src/services/editor.ts +376 -136
  64. package/src/services/history.ts +306 -209
  65. package/src/services/ui.ts +7 -0
  66. package/src/theme/history-list-panel.scss +72 -5
  67. package/src/theme/page-bar.scss +0 -4
  68. package/src/theme/style-setter/border.scss +4 -1
  69. package/src/type.ts +183 -64
  70. package/src/utils/data-source/index.ts +2 -0
  71. package/src/utils/editor.ts +166 -60
  72. package/src/utils/history.ts +308 -0
  73. package/src/utils/index.ts +2 -0
  74. package/src/utils/indexed-db.ts +122 -0
  75. package/src/utils/undo-redo.ts +88 -0
  76. package/types/index.d.ts +813 -302
@@ -4917,8 +4917,41 @@
4917
4917
  };
4918
4918
  var props_default = new Props();
4919
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
4920
4927
  //#region packages/editor/src/utils/undo-redo.ts
4921
- 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
+ }
4922
4955
  elementList;
4923
4956
  listCursor;
4924
4957
  listMaxSize;
@@ -4928,6 +4961,17 @@
4928
4961
  this.listCursor = 0;
4929
4962
  this.listMaxSize = listMaxSize > minListMaxSize ? listMaxSize : minListMaxSize;
4930
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
+ }
4931
4975
  pushElement(element) {
4932
4976
  this.elementList.splice(this.listCursor, this.elementList.length - this.listCursor, cloneDeep$1(element));
4933
4977
  this.listCursor += 1;
@@ -4960,6 +5004,28 @@
4960
5004
  return cloneDeep$1(this.elementList[this.listCursor - 1]);
4961
5005
  }
4962
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
+ /**
4963
5029
  * 返回栈内全部元素的浅克隆数组(按时间顺序,索引 0 为最早一步)。
4964
5030
  * 仅用于历史面板等只读展示场景,不应直接修改返回值。
4965
5031
  */
@@ -4981,172 +5047,325 @@
4981
5047
  }
4982
5048
  };
4983
5049
  //#endregion
4984
- //#region packages/editor/src/services/history.ts
4985
- var History = class History extends BaseService {
4986
- /**
4987
- * 把单个代码块栈拆成若干 group:
4988
- * - 把"新增/删除"独立成组(语义上属于一次性事件,不应与 update 合并);
4989
- * - 连续 'update' 合并到同一组,组内 steps 顺序就是发生顺序。
4990
- */
4991
- static mergeCodeBlockSteps(codeBlockId, list, cursor) {
4992
- const groups = [];
4993
- let current = null;
4994
- const currentIndex = cursor - 1;
4995
- list.forEach((step, index) => {
4996
- const opType = History.detectOpType(step.oldContent, step.newContent);
4997
- const applied = index < cursor;
4998
- const isCurrent = index === currentIndex;
4999
- if (opType === "update" && current?.opType === "update") {
5000
- current.steps.push({
5001
- step,
5002
- index,
5003
- applied,
5004
- isCurrent
5005
- });
5006
- current.applied = applied;
5007
- if (isCurrent) current.isCurrent = true;
5008
- } else {
5009
- current = {
5010
- kind: "code-block",
5011
- id: codeBlockId,
5012
- opType,
5013
- steps: [{
5014
- step,
5015
- index,
5016
- applied,
5017
- isCurrent
5018
- }],
5019
- applied,
5020
- isCurrent
5021
- };
5022
- groups.push(current);
5023
- }
5024
- });
5025
- return groups;
5026
- }
5027
- static mergeDataSourceSteps(dataSourceId, list, cursor) {
5028
- const groups = [];
5029
- let current = null;
5030
- const currentIndex = cursor - 1;
5031
- list.forEach((step, index) => {
5032
- const opType = History.detectOpType(step.oldSchema, step.newSchema);
5033
- const applied = index < cursor;
5034
- const isCurrent = index === currentIndex;
5035
- if (opType === "update" && current?.opType === "update") {
5036
- current.steps.push({
5037
- step,
5038
- index,
5039
- applied,
5040
- isCurrent
5041
- });
5042
- current.applied = applied;
5043
- if (isCurrent) current.isCurrent = true;
5044
- } else {
5045
- current = {
5046
- kind: "data-source",
5047
- id: dataSourceId,
5048
- opType,
5049
- steps: [{
5050
- step,
5051
- index,
5052
- applied,
5053
- isCurrent
5054
- }],
5055
- applied,
5056
- isCurrent
5057
- };
5058
- groups.push(current);
5059
- }
5060
- });
5061
- return groups;
5062
- }
5063
- /**
5064
- * 根据 old/new 是否为 null 推断 opType(与 push 时的约定一致)。
5065
- */
5066
- static detectOpType(oldVal, newVal) {
5067
- if (oldVal === null && newVal !== null) return "add";
5068
- if (oldVal !== null && newVal === null) return "remove";
5069
- return "update";
5070
- }
5071
- /**
5072
- * 把页面栈拆成若干 group:
5073
- * - 单节点的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group;
5074
- * - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单节点修改组);
5075
- * - 多节点 'update'(如批量改属性)也独立成组(无明确单一目标,避免误合并)。
5076
- */
5077
- static mergePageSteps(pageId, list, cursor) {
5078
- const groups = [];
5079
- let current = null;
5080
- const currentIndex = cursor - 1;
5081
- list.forEach((step, index) => {
5082
- const applied = index < cursor;
5083
- const isCurrent = index === currentIndex;
5084
- const targetId = History.detectPageTargetId(step);
5085
- const targetName = History.detectPageTargetName(step);
5086
- const entry = {
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
+ *
5117
+ * 代码块与数据源除 `kind` 外结构完全一致,统一由本方法处理;`kind` 决定返回的具体分组类型。
5118
+ */
5119
+ var mergeStackSteps = (kind, id, list, cursor) => {
5120
+ const currentIndex = cursor - 1;
5121
+ return list.map((step, index) => {
5122
+ const applied = index < cursor;
5123
+ const isCurrent = index === currentIndex;
5124
+ return {
5125
+ kind,
5126
+ id,
5127
+ opType: step.opType,
5128
+ steps: [{
5087
5129
  step,
5088
5130
  index,
5089
5131
  applied,
5090
5132
  isCurrent
5133
+ }],
5134
+ applied,
5135
+ isCurrent
5136
+ };
5137
+ });
5138
+ };
5139
+ /**
5140
+ * 把页面栈拆成若干 group:
5141
+ * - 单节点的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group;
5142
+ * - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单节点修改组);
5143
+ * - 多节点 'update'(如批量改属性)也独立成组(无明确单一目标,避免误合并)。
5144
+ */
5145
+ var mergePageSteps = (pageId, list, cursor) => {
5146
+ const groups = [];
5147
+ let current = null;
5148
+ const currentIndex = cursor - 1;
5149
+ list.forEach((step, index) => {
5150
+ const applied = index < cursor;
5151
+ const isCurrent = index === currentIndex;
5152
+ const targetId = detectPageTargetId(step);
5153
+ const targetName = detectPageTargetName(step);
5154
+ const entry = {
5155
+ step,
5156
+ index,
5157
+ applied,
5158
+ isCurrent
5159
+ };
5160
+ const mergeable = step.opType === "update" && targetId !== void 0;
5161
+ if (mergeable && current?.opType === "update" && current.targetId === targetId) {
5162
+ current.steps.push(entry);
5163
+ current.applied = applied;
5164
+ if (isCurrent) current.isCurrent = true;
5165
+ if (targetName) current.targetName = targetName;
5166
+ } else {
5167
+ current = {
5168
+ kind: "page",
5169
+ pageId,
5170
+ opType: step.opType,
5171
+ targetId: mergeable ? targetId : void 0,
5172
+ targetName,
5173
+ steps: [entry],
5174
+ applied,
5175
+ isCurrent
5091
5176
  };
5092
- const mergeable = step.opType === "update" && targetId !== void 0;
5093
- if (mergeable && current?.opType === "update" && current.targetId === targetId) {
5094
- current.steps.push(entry);
5095
- current.applied = applied;
5096
- if (isCurrent) current.isCurrent = true;
5097
- if (targetName) current.targetName = targetName;
5098
- } else {
5099
- current = {
5100
- kind: "page",
5101
- pageId,
5102
- opType: step.opType,
5103
- targetId: mergeable ? targetId : void 0,
5104
- targetName,
5105
- steps: [entry],
5106
- applied,
5107
- isCurrent
5108
- };
5109
- groups.push(current);
5110
- }
5111
- });
5112
- return groups;
5177
+ groups.push(current);
5178
+ }
5179
+ });
5180
+ return groups;
5181
+ };
5182
+ /**
5183
+ * 解析 StepValue 中的"目标节点 id"用于合并:
5184
+ * - 单节点 update:取唯一一项 updatedItems 的节点 id;
5185
+ * - 其它情形(多节点 update / add / remove):返回 undefined,表示不参与合并。
5186
+ */
5187
+ var detectPageTargetId = (step) => {
5188
+ if (step.opType !== "update") return void 0;
5189
+ const items = step.diff;
5190
+ if (items?.length !== 1) return void 0;
5191
+ return items[0].newSchema?.id ?? items[0].oldSchema?.id;
5192
+ };
5193
+ /** 解析 StepValue 中的目标节点可读名(用于 UI 展示)。 */
5194
+ var detectPageTargetName = (step) => {
5195
+ const items = step.diff;
5196
+ if (step.opType === "update") {
5197
+ if (items?.length === 1) {
5198
+ const node = items[0].newSchema || items[0].oldSchema;
5199
+ return node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : void 0);
5200
+ }
5201
+ return items?.length ? `${items.length} 个节点` : void 0;
5113
5202
  }
5114
- /**
5115
- * 解析 StepValue 中的"目标节点 id"用于合并:
5116
- * - 单节点 update:取唯一一项 updatedItems 的节点 id;
5117
- * - 其它情形(多节点 update / add / remove):返回 undefined,表示不参与合并。
5118
- */
5119
- static detectPageTargetId(step) {
5120
- if (step.opType !== "update") return void 0;
5121
- const items = step.updatedItems;
5122
- if (items?.length !== 1) return void 0;
5123
- return items[0].newNode?.id ?? items[0].oldNode?.id;
5124
- }
5125
- /** 解析 StepValue 中的目标节点可读名(用于 UI 展示)。 */
5126
- static detectPageTargetName(step) {
5127
- if (step.opType === "update") {
5128
- const items = step.updatedItems;
5129
- if (items?.length === 1) {
5130
- const node = items[0].newNode || items[0].oldNode;
5131
- return node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : void 0);
5132
- }
5133
- return items?.length ? `${items.length} 个节点` : void 0;
5203
+ if (step.opType === "add") {
5204
+ if (items?.length === 1) {
5205
+ const n = items[0].newSchema;
5206
+ return n?.name || n?.type || `${n?.id}`;
5134
5207
  }
5135
- if (step.opType === "add") {
5136
- if (step.nodes?.length === 1) {
5137
- const n = step.nodes[0];
5138
- return n.name || n.type || `${n.id}`;
5139
- }
5140
- return step.nodes?.length ? `${step.nodes.length} 个节点` : void 0;
5208
+ return items?.length ? `${items.length} 个节点` : void 0;
5209
+ }
5210
+ if (step.opType === "remove") {
5211
+ if (items?.length === 1) {
5212
+ const n = items[0].oldSchema;
5213
+ return n?.name || n?.type || `${n?.id}`;
5141
5214
  }
5142
- if (step.opType === "remove") {
5143
- if (step.removedItems?.length === 1) {
5144
- const n = step.removedItems[0].node;
5145
- return n.name || n.type || `${n.id}`;
5146
- }
5147
- return step.removedItems?.length ? `${step.removedItems.length} 个节点` : void 0;
5215
+ return items?.length ? `${items.length} 个节点` : void 0;
5216
+ }
5217
+ };
5218
+ /**
5219
+ * 把 `Record<Id, UndoRedo>` 整体序列化为 `Record<Id, SerializedUndoRedo>`。
5220
+ *
5221
+ * 序列化(深克隆)的同一趟里,只把每条 step 中可能含函数的 `diff` 用 serialize-javascript 序列化成字符串,
5222
+ * 其余字段(uuid / opType / timestamp / `modifiedNodeIds` Map 等)原样保留,交给 IndexedDB 结构化克隆。
5223
+ * 这样既能写入函数,又避免序列化整份快照的开销;读取时再由 {@link parseStacksStepDiff} 还原 diff。
5224
+ * 不含 `diff` 的元素(如通用栈)原样透传。
5225
+ */
5226
+ var serializeStacks = (stacks) => {
5227
+ const result = {};
5228
+ Object.entries(stacks).forEach(([id, undoRedo]) => {
5229
+ if (!undoRedo) return;
5230
+ const serialized = undoRedo.serialize();
5231
+ result[id] = {
5232
+ ...serialized,
5233
+ elementList: serialized.elementList.map((step) => step.diff === void 0 ? step : Object.assign({}, step, { diff: (0, serialize_javascript.default)(step.diff) }))
5234
+ };
5235
+ });
5236
+ return result;
5237
+ };
5238
+ /**
5239
+ * 把 `Record<Id, SerializedUndoRedo>` 整体还原为 `Record<Id, UndoRedo>`。
5240
+ * 还原时把每个栈的游标定位到最近一条已保存(`saved === true`)记录之后。
5241
+ *
5242
+ * 与 {@link serializeStacks} 相反:当传入 `parse`(parseDSL)时,把每条 step 中以字符串形式存储的 `diff`
5243
+ * 解析回真实对象(含函数);不含 `diff` 的元素(如通用栈)原样透传。
5244
+ */
5245
+ var deserializeStacks = (stacks = {}, parse) => {
5246
+ const result = {};
5247
+ Object.entries(stacks).forEach(([id, serialized]) => {
5248
+ if (!serialized) return;
5249
+ const elementList = parse ? serialized.elementList.map((step) => {
5250
+ const { diff } = step;
5251
+ return typeof diff === "string" ? Object.assign({}, step, { diff: parse(`(${diff})`) }) : step;
5252
+ }) : serialized.elementList;
5253
+ result[id] = UndoRedo.fromSerialized({
5254
+ ...serialized,
5255
+ elementList
5256
+ }, { isSavedStep: (element) => element.saved === true });
5257
+ });
5258
+ return result;
5259
+ };
5260
+ /**
5261
+ * 按 id 从「按 id 分栈」的记录表(代码块 / 数据源)中获取(或创建)对应的 UndoRedo 栈。
5262
+ */
5263
+ var getOrCreateStack = (stacks, id) => {
5264
+ if (!stacks[id]) stacks[id] = new UndoRedo();
5265
+ return stacks[id];
5266
+ };
5267
+ /**
5268
+ * 撤销下限:当页面栈 index 0 是 `opType: 'initial'` 的基线 step 时为 1(基线不可被撤销),否则为 0。
5269
+ * 用于把 cursor 钉在基线之上,保证 undo / canUndo / goto 都不会越过初始基线。
5270
+ */
5271
+ var undoFloor = (undoRedo) => {
5272
+ return undoRedo.getElementList()[0]?.opType === "initial" ? 1 : 0;
5273
+ };
5274
+ /** 将单次 push 产生的 history uuid(或 null)转为 *AndGetHistoryId 返回用的 uuid 列表。 */
5275
+ var getLastPushedHistoryIds = (historyId) => historyId ? [historyId] : [];
5276
+ //#endregion
5277
+ //#region packages/editor/src/utils/indexed-db.ts
5278
+ /**
5279
+ * 一组极简的、基于原生 IndexedDB 的 Promise KV 工具,避免引入额外依赖。
5280
+ * 仅用于浏览器环境;在不支持 IndexedDB 的环境(如 SSR / 部分测试环境)下会 reject。
5281
+ */
5282
+ /** 是否处于支持 IndexedDB 的环境。 */
5283
+ var isIndexedDBSupported = () => typeof indexedDB !== "undefined" && indexedDB !== null;
5284
+ /**
5285
+ * 打开(必要时升级)数据库,确保目标 objectStore 存在后返回连接。
5286
+ *
5287
+ * 由于 objectStore 只能在 `onupgradeneeded` 内创建,这里先以当前版本打开,
5288
+ * 若发现 store 不存在则关闭连接、以更高版本重开来按需创建,兼容动态 storeName。
5289
+ */
5290
+ var openIndexedDB = (dbName, storeName) => new Promise((resolve, reject) => {
5291
+ if (!isIndexedDBSupported()) {
5292
+ reject(/* @__PURE__ */ new Error("当前环境不支持 IndexedDB"));
5293
+ return;
5294
+ }
5295
+ const request = indexedDB.open(dbName);
5296
+ request.onupgradeneeded = () => {
5297
+ const db = request.result;
5298
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
5299
+ };
5300
+ request.onerror = () => reject(request.error);
5301
+ request.onsuccess = () => {
5302
+ const db = request.result;
5303
+ if (db.objectStoreNames.contains(storeName)) {
5304
+ resolve(db);
5305
+ return;
5148
5306
  }
5307
+ const nextVersion = db.version + 1;
5308
+ db.close();
5309
+ const upgradeRequest = indexedDB.open(dbName, nextVersion);
5310
+ upgradeRequest.onupgradeneeded = () => {
5311
+ const upgradeDb = upgradeRequest.result;
5312
+ if (!upgradeDb.objectStoreNames.contains(storeName)) upgradeDb.createObjectStore(storeName);
5313
+ };
5314
+ upgradeRequest.onerror = () => reject(upgradeRequest.error);
5315
+ upgradeRequest.onsuccess = () => resolve(upgradeRequest.result);
5316
+ };
5317
+ });
5318
+ /** 写入(覆盖)一条记录。value 通过结构化克隆存储,支持 Map / Set 等结构。 */
5319
+ var idbSet = async (dbName, storeName, key, value) => {
5320
+ const db = await openIndexedDB(dbName, storeName);
5321
+ try {
5322
+ await new Promise((resolve, reject) => {
5323
+ const tx = db.transaction(storeName, "readwrite");
5324
+ tx.objectStore(storeName).put(value, key);
5325
+ tx.oncomplete = () => resolve();
5326
+ tx.onabort = () => reject(tx.error);
5327
+ tx.onerror = () => reject(tx.error);
5328
+ });
5329
+ } finally {
5330
+ db.close();
5331
+ }
5332
+ };
5333
+ /** 读取一条记录,不存在时返回 undefined。 */
5334
+ var idbGet = async (dbName, storeName, key) => {
5335
+ const db = await openIndexedDB(dbName, storeName);
5336
+ try {
5337
+ return await new Promise((resolve, reject) => {
5338
+ const request = db.transaction(storeName, "readonly").objectStore(storeName).get(key);
5339
+ request.onsuccess = () => resolve(request.result);
5340
+ request.onerror = () => reject(request.error);
5341
+ });
5342
+ } finally {
5343
+ db.close();
5344
+ }
5345
+ };
5346
+ /** 删除一条记录。 */
5347
+ var idbDelete = async (dbName, storeName, key) => {
5348
+ const db = await openIndexedDB(dbName, storeName);
5349
+ try {
5350
+ await new Promise((resolve, reject) => {
5351
+ const tx = db.transaction(storeName, "readwrite");
5352
+ tx.objectStore(storeName).delete(key);
5353
+ tx.oncomplete = () => resolve();
5354
+ tx.onabort = () => reject(tx.error);
5355
+ tx.onerror = () => reject(tx.error);
5356
+ });
5357
+ } finally {
5358
+ db.close();
5149
5359
  }
5360
+ };
5361
+ //#endregion
5362
+ //#region packages/editor/src/services/history.ts
5363
+ /** 历史记录持久化快照的默认存储位置与结构版本。 */
5364
+ var DEFAULT_DB_NAME = "tmagic-editor";
5365
+ var DEFAULT_STORE_NAME = "history";
5366
+ var DEFAULT_KEY = "default";
5367
+ var PERSIST_VERSION = 2;
5368
+ var History = class extends BaseService {
5150
5369
  state = (0, vue.reactive)({
5151
5370
  pageSteps: {},
5152
5371
  pageId: void 0,
@@ -5186,6 +5405,53 @@
5186
5405
  this.state.dataSourceState = {};
5187
5406
  }
5188
5407
  /**
5408
+ * 为指定页面 / 页面片种入一条「初始基线」记录(如加载 DSL 时的「初始 / 加载」基线)。
5409
+ *
5410
+ * 该记录是一条 `opType: 'initial'` 的 {@link StepValue},作为页面历史栈 **index 0 的固定底线**:
5411
+ * - 它是一条真实入栈的 step(随栈一起持久化),但被钉为撤销/回滚的下限——cursor 永不低于它,
5412
+ * 因此不会被 undo / goto / revert 触达(详见 {@link undo} / {@link setCanUndoRedo});
5413
+ * - 历史面板把它过滤出分组列表(见 {@link getPageHistoryGroups}),改由底部「初始」行展示。
5414
+ *
5415
+ * 仅当目标页面栈为空时种入(保证 initial 一定位于 index 0);已存在 initial 底线时默认不重复种入,
5416
+ * 传 `force=true` 且栈为空时按新基线种入。
5417
+ */
5418
+ setPageMarker(pageId, options = {}) {
5419
+ if (pageId === void 0 || pageId === null || `${pageId}` === "") return null;
5420
+ const existing = this.getPageMarker(pageId);
5421
+ if (existing) return existing;
5422
+ const stack = getOrCreateStack(this.state.pageSteps, pageId);
5423
+ if (stack.getLength() > 0) return null;
5424
+ const marker = {
5425
+ uuid: (0, _tmagic_utils.guid)(),
5426
+ opType: "initial",
5427
+ diff: [],
5428
+ data: {
5429
+ name: options.name || "",
5430
+ id: pageId
5431
+ },
5432
+ selectedBefore: [],
5433
+ selectedAfter: [],
5434
+ modifiedNodeIds: /* @__PURE__ */ new Map(),
5435
+ historyDescription: options.description || "未修改的初始状态",
5436
+ timestamp: Date.now(),
5437
+ ...options.source ? { source: options.source } : {}
5438
+ };
5439
+ stack.pushElement(marker);
5440
+ if (`${pageId}` === `${this.state.pageId}`) this.setCanUndoRedo();
5441
+ this.emit("page-marker-change", marker);
5442
+ return marker;
5443
+ }
5444
+ /**
5445
+ * 读取指定页面(缺省当前活动页)的初始基线 step(页面栈 index 0 且 `opType: 'initial'`);
5446
+ * 不存在时返回 undefined。
5447
+ */
5448
+ getPageMarker(pageId) {
5449
+ const targetPageId = pageId ?? this.state.pageId;
5450
+ if (!targetPageId) return void 0;
5451
+ const first = this.state.pageSteps[targetPageId]?.getElementList()[0];
5452
+ return first?.opType === "initial" ? first : void 0;
5453
+ }
5454
+ /**
5189
5455
  * 把一条步骤推入指定页面的栈;不指定 pageId 时落到当前活动页。
5190
5456
  *
5191
5457
  * 跨页操作(例如 `moveToContainer` 把节点搬到其它页)必须显式传入 `pageId`,
@@ -5194,11 +5460,31 @@
5194
5460
  push(state, pageId) {
5195
5461
  const undoRedo = this.getUndoRedo(pageId);
5196
5462
  if (!undoRedo) return null;
5463
+ if (state.uuid === void 0) state.uuid = (0, _tmagic_utils.guid)();
5197
5464
  if (state.timestamp === void 0) state.timestamp = Date.now();
5198
5465
  undoRedo.pushElement(state);
5199
5466
  if (pageId === void 0 || `${pageId}` === `${this.state.pageId}`) this.emit("change", state);
5200
5467
  return state;
5201
5468
  }
5469
+ /** 读取指定页面(缺省当前活动页)历史栈当前游标所在的 step(cursor - 1);无则返回 null。 */
5470
+ getCurrentPageStep(pageId) {
5471
+ const targetPageId = pageId ?? this.state.pageId;
5472
+ if (!targetPageId) return null;
5473
+ return this.state.pageSteps[targetPageId]?.getCurrentElement() ?? null;
5474
+ }
5475
+ /**
5476
+ * 用 `state` 替换指定页面栈当前游标所在的 step(并丢弃其后的重做尾部),游标不变。
5477
+ * 用于「连续 set root 记录合并」等就地替换最新一条的场景;替换成功后按需刷新 / 通知。
5478
+ */
5479
+ replaceCurrentPageStep(state, pageId) {
5480
+ const undoRedo = this.getUndoRedo(pageId);
5481
+ if (!undoRedo) return null;
5482
+ if (state.uuid === void 0) state.uuid = (0, _tmagic_utils.guid)();
5483
+ if (state.timestamp === void 0) state.timestamp = Date.now();
5484
+ if (!undoRedo.replaceCurrentElement(state)) return null;
5485
+ this.emit("change", state);
5486
+ return state;
5487
+ }
5202
5488
  /**
5203
5489
  * 推入一条代码块变更记录(与页面/节点完全无关),按 `codeBlockId` 维度独立一份 UndoRedo 栈。
5204
5490
  *
@@ -5209,17 +5495,15 @@
5209
5495
  * - 不直接驱动 codeBlockService,调用方负责实际写回。
5210
5496
  */
5211
5497
  pushCodeBlock(codeBlockId, payload) {
5212
- if (!codeBlockId) return null;
5213
- const step = {
5214
- id: codeBlockId,
5215
- oldContent: payload.oldContent ? cloneDeep$1(payload.oldContent) : null,
5216
- newContent: payload.newContent ? cloneDeep$1(payload.newContent) : null,
5217
- changeRecords: payload.changeRecords?.length ? cloneDeep$1(payload.changeRecords) : void 0,
5498
+ const step = createStackStep(codeBlockId, {
5499
+ oldValue: payload.oldContent,
5500
+ newValue: payload.newContent,
5501
+ changeRecords: payload.changeRecords,
5218
5502
  historyDescription: payload.historyDescription,
5219
- source: payload.source,
5220
- timestamp: Date.now()
5221
- };
5222
- this.getCodeBlockUndoRedo(codeBlockId).pushElement(step);
5503
+ source: payload.source
5504
+ });
5505
+ if (!step) return null;
5506
+ getOrCreateStack(this.state.codeBlockState, codeBlockId).pushElement(step);
5223
5507
  this.emit("code-block-history-change", codeBlockId, step);
5224
5508
  return step;
5225
5509
  }
@@ -5228,17 +5512,15 @@
5228
5512
  * 行为同 pushCodeBlock(新增 oldSchema=null;删除 newSchema=null)。
5229
5513
  */
5230
5514
  pushDataSource(dataSourceId, payload) {
5231
- if (!dataSourceId) return null;
5232
- const step = {
5233
- id: dataSourceId,
5234
- oldSchema: payload.oldSchema ? cloneDeep$1(payload.oldSchema) : null,
5235
- newSchema: payload.newSchema ? cloneDeep$1(payload.newSchema) : null,
5236
- changeRecords: payload.changeRecords?.length ? cloneDeep$1(payload.changeRecords) : void 0,
5515
+ const step = createStackStep(dataSourceId, {
5516
+ oldValue: payload.oldSchema,
5517
+ newValue: payload.newSchema,
5518
+ changeRecords: payload.changeRecords,
5237
5519
  historyDescription: payload.historyDescription,
5238
- source: payload.source,
5239
- timestamp: Date.now()
5240
- };
5241
- this.getDataSourceUndoRedo(dataSourceId).pushElement(step);
5520
+ source: payload.source
5521
+ });
5522
+ if (!step) return null;
5523
+ getOrCreateStack(this.state.dataSourceState, dataSourceId).pushElement(step);
5242
5524
  this.emit("data-source-history-change", dataSourceId, step);
5243
5525
  return step;
5244
5526
  }
@@ -5293,6 +5575,7 @@
5293
5575
  undo() {
5294
5576
  const undoRedo = this.getUndoRedo();
5295
5577
  if (!undoRedo) return null;
5578
+ if (undoRedo.getCursor() <= undoFloor(undoRedo)) return null;
5296
5579
  const state = undoRedo.undo();
5297
5580
  this.emit("change", state);
5298
5581
  return state;
@@ -5310,6 +5593,126 @@
5310
5593
  this.removeAllPlugins();
5311
5594
  }
5312
5595
  /**
5596
+ * 清空指定页面(缺省当前活动页)的历史记录栈。
5597
+ * 仅删除撤销/重做记录,不会改动当前 DSL;清空后该页将无法再撤销/重做之前的操作。
5598
+ */
5599
+ clearPage(pageId) {
5600
+ const targetPageId = pageId ?? this.state.pageId;
5601
+ if (!targetPageId) return;
5602
+ const marker = this.getPageMarker(targetPageId);
5603
+ this.state.pageSteps[targetPageId] = new UndoRedo();
5604
+ if (marker) this.setPageMarker(targetPageId, {
5605
+ name: marker.data?.name,
5606
+ description: marker.historyDescription,
5607
+ source: marker.source
5608
+ });
5609
+ if (`${targetPageId}` === `${this.state.pageId}`) {
5610
+ this.setCanUndoRedo();
5611
+ this.emit("clear-page", null);
5612
+ }
5613
+ }
5614
+ /**
5615
+ * 清空数据源历史记录栈:传入 `dataSourceId` 仅清空该数据源,缺省清空全部数据源。
5616
+ * 仅删除撤销/重做记录,不会改动数据源本身。
5617
+ */
5618
+ clearDataSource(dataSourceId) {
5619
+ if (dataSourceId !== void 0) delete this.state.dataSourceState[dataSourceId];
5620
+ else this.state.dataSourceState = {};
5621
+ }
5622
+ /**
5623
+ * 清空代码块历史记录栈:传入 `codeBlockId` 仅清空该代码块,缺省清空全部代码块。
5624
+ * 仅删除撤销/重做记录,不会改动代码块本身。
5625
+ */
5626
+ clearCodeBlock(codeBlockId) {
5627
+ if (codeBlockId !== void 0) delete this.state.codeBlockState[codeBlockId];
5628
+ else this.state.codeBlockState = {};
5629
+ }
5630
+ /**
5631
+ * 标记「整份 DSL 已保存」:把页面 / 代码块 / 数据源所有栈当前游标所在的记录都标为 `saved`。
5632
+ * 适用于「整体落库」场景;若只保存了其中一类,请改用更细粒度的
5633
+ * {@link markPageSaved} / {@link markCodeBlockSaved} / {@link markDataSourceSaved}。
5634
+ */
5635
+ markSaved() {
5636
+ Object.values(this.state.pageSteps).forEach(markStackSaved);
5637
+ Object.values(this.state.codeBlockState).forEach(markStackSaved);
5638
+ Object.values(this.state.dataSourceState).forEach(markStackSaved);
5639
+ this.emit("mark-saved", { kind: "all" });
5640
+ }
5641
+ /**
5642
+ * 标记指定页面(缺省为当前活动页)的历史栈当前记录为已保存。
5643
+ * 仅影响该页面自己的栈,不波及代码块 / 数据源 / 其它页面。
5644
+ */
5645
+ markPageSaved(pageId) {
5646
+ const targetPageId = pageId ?? this.state.pageId;
5647
+ if (!targetPageId) return;
5648
+ markStackSaved(this.state.pageSteps[targetPageId]);
5649
+ this.emit("mark-saved", {
5650
+ kind: "page",
5651
+ id: targetPageId
5652
+ });
5653
+ }
5654
+ /** 标记指定代码块的历史栈当前记录为已保存,仅影响该代码块自己的栈。 */
5655
+ markCodeBlockSaved(codeBlockId) {
5656
+ if (!codeBlockId) return;
5657
+ markStackSaved(this.state.codeBlockState[codeBlockId]);
5658
+ this.emit("mark-saved", {
5659
+ kind: "code-block",
5660
+ id: codeBlockId
5661
+ });
5662
+ }
5663
+ /** 标记指定数据源的历史栈当前记录为已保存,仅影响该数据源自己的栈。 */
5664
+ markDataSourceSaved(dataSourceId) {
5665
+ if (!dataSourceId) return;
5666
+ markStackSaved(this.state.dataSourceState[dataSourceId]);
5667
+ this.emit("mark-saved", {
5668
+ kind: "data-source",
5669
+ id: dataSourceId
5670
+ });
5671
+ }
5672
+ /**
5673
+ * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源)序列化后写入本地 IndexedDB。
5674
+ *
5675
+ * - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
5676
+ * - `key` 用于区分不同活动页 / 项目(同一 store 下可保存多份快照),缺省为 `default`;
5677
+ * - 返回写入成功的快照对象,便于调用方记录 savedAt 等信息;
5678
+ * - 不支持 IndexedDB 的环境(如 SSR)会 reject。
5679
+ */
5680
+ async saveToIndexedDB(options = {}) {
5681
+ const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
5682
+ const snapshot = {
5683
+ version: PERSIST_VERSION,
5684
+ pageId: this.state.pageId,
5685
+ pageSteps: serializeStacks(this.state.pageSteps),
5686
+ codeBlockState: serializeStacks(this.state.codeBlockState),
5687
+ dataSourceState: serializeStacks(this.state.dataSourceState),
5688
+ savedAt: Date.now()
5689
+ };
5690
+ await idbSet(this.resolveDbName(dbName, appId), storeName, key, snapshot);
5691
+ this.emit("save-to-indexed-db", snapshot);
5692
+ return snapshot;
5693
+ }
5694
+ /**
5695
+ * 从本地 IndexedDB 读取此前保存的历史快照并重建全部撤销/重做栈。
5696
+ *
5697
+ * - 读取到的每个栈都会经 {@link UndoRedo.fromSerialized} 还原(含游标),随后可直接 undo/redo;
5698
+ * - 会整体覆盖当前内存中的历史状态,并把活动页恢复为快照中的 pageId;
5699
+ * - 找不到对应记录时返回 null,且不改动当前状态;
5700
+ * - 不支持 IndexedDB 的环境(如 SSR)会 reject。
5701
+ */
5702
+ async restoreFromIndexedDB(options = {}) {
5703
+ const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
5704
+ const snapshot = await idbGet(this.resolveDbName(dbName, appId), storeName, key);
5705
+ if (!snapshot) return null;
5706
+ const parseDSL = getEditorConfig("parseDSL");
5707
+ this.state.pageSteps = deserializeStacks(snapshot.pageSteps, parseDSL);
5708
+ this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState, parseDSL);
5709
+ this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState, parseDSL);
5710
+ this.state.pageId = snapshot.pageId;
5711
+ this.setCanUndoRedo();
5712
+ this.emit("restore-from-indexed-db", snapshot);
5713
+ return snapshot;
5714
+ }
5715
+ /**
5313
5716
  * 取出当前活动页的历史步骤平铺列表(包含已应用 + 已撤销)。
5314
5717
  * 列表按时间正序,最早一步在最前面。
5315
5718
  * 通常 UI 应使用 `getPageHistoryGroups` 取已合并分组的版本;本方法仅为兼容/调试保留。
@@ -5340,15 +5743,11 @@
5340
5743
  if (!undoRedo) return [];
5341
5744
  const list = undoRedo.getElementList();
5342
5745
  if (!list.length) return [];
5343
- const cursor = undoRedo.getCursor();
5344
- return History.mergePageSteps(targetPageId, list, cursor);
5746
+ return mergePageSteps(targetPageId, list, undoRedo.getCursor()).filter((group) => group.opType !== "initial");
5345
5747
  }
5346
5748
  /**
5347
- * 取出全部代码块的历史栈,按 codeBlockId 分组。
5348
- * 同一栈内相邻、同 opType 且作用于同一 id 的多步会被合并为一个 group:
5349
- * - 这正是"代码块/数据源各自按 id 分栈"的天然表现,再叠加"连续修改同目标的相邻步骤合并展示"。
5350
- * - 合并后 group 暴露子步骤数组,UI 可展开查看每一步的 changeRecords。
5351
- * - applied 字段:组内最后一步是否处于已应用段。
5749
+ * 取出全部代码块的历史栈,按 codeBlockId 分桶展示。
5750
+ * 同一栈内每条操作记录独立成组,不做相邻 update 合并。
5352
5751
  */
5353
5752
  getCodeBlockHistoryGroups() {
5354
5753
  const groups = [];
@@ -5357,7 +5756,7 @@
5357
5756
  const list = undoRedo.getElementList();
5358
5757
  if (!list.length) return;
5359
5758
  const cursor = undoRedo.getCursor();
5360
- groups.push(...History.mergeCodeBlockSteps(id, list, cursor));
5759
+ groups.push(...mergeStackSteps("code-block", id, list, cursor));
5361
5760
  });
5362
5761
  return groups;
5363
5762
  }
@@ -5407,7 +5806,45 @@
5407
5806
  }));
5408
5807
  }
5409
5808
  /**
5410
- * 取出全部数据源的历史栈,按 dataSourceId 分组。同上。
5809
+ * 按历史记录 uuid 在指定页面(默认当前活动页)的栈中查找其索引。
5810
+ * 找不到时返回 -1。供「按 uuid 回滚」等需要把 uuid 映射回 index 的场景使用。
5811
+ */
5812
+ getPageStepIndexByUuid(uuid, pageId) {
5813
+ if (!uuid) return -1;
5814
+ return this.getPageStepList(pageId).findIndex((entry) => entry.step.uuid === uuid);
5815
+ }
5816
+ /**
5817
+ * 按历史记录 uuid 在全部代码块栈中查找其所属 codeBlockId 与索引。
5818
+ * 找不到时返回 null。
5819
+ */
5820
+ findCodeBlockStepLocationByUuid(uuid) {
5821
+ if (!uuid) return null;
5822
+ for (const id of Object.keys(this.state.codeBlockState)) {
5823
+ const index = this.getCodeBlockStepList(id).findIndex((entry) => entry.step.uuid === uuid);
5824
+ if (index >= 0) return {
5825
+ id,
5826
+ index
5827
+ };
5828
+ }
5829
+ return null;
5830
+ }
5831
+ /**
5832
+ * 按历史记录 uuid 在全部数据源栈中查找其所属 dataSourceId 与索引。
5833
+ * 找不到时返回 null。
5834
+ */
5835
+ findDataSourceStepLocationByUuid(uuid) {
5836
+ if (!uuid) return null;
5837
+ for (const id of Object.keys(this.state.dataSourceState)) {
5838
+ const index = this.getDataSourceStepList(id).findIndex((entry) => entry.step.uuid === uuid);
5839
+ if (index >= 0) return {
5840
+ id,
5841
+ index
5842
+ };
5843
+ }
5844
+ return null;
5845
+ }
5846
+ /**
5847
+ * 取出全部数据源的历史栈,按 dataSourceId 分桶展示。同上,每条操作独立成组。
5411
5848
  */
5412
5849
  getDataSourceHistoryGroups() {
5413
5850
  const groups = [];
@@ -5416,7 +5853,7 @@
5416
5853
  const list = undoRedo.getElementList();
5417
5854
  if (!list.length) return;
5418
5855
  const cursor = undoRedo.getCursor();
5419
- groups.push(...History.mergeDataSourceSteps(id, list, cursor));
5856
+ groups.push(...mergeStackSteps("data-source", id, list, cursor));
5420
5857
  });
5421
5858
  return groups;
5422
5859
  }
@@ -5432,35 +5869,22 @@
5432
5869
  if (!this.state.pageSteps[targetPageId]) this.state.pageSteps[targetPageId] = new UndoRedo();
5433
5870
  return this.state.pageSteps[targetPageId];
5434
5871
  }
5435
- setCanUndoRedo() {
5436
- const undoRedo = this.getUndoRedo();
5437
- this.state.canRedo = undoRedo?.canRedo() || false;
5438
- this.state.canUndo = undoRedo?.canUndo() || false;
5439
- }
5440
5872
  /**
5441
- * id 获取(或创建)指定代码块的 UndoRedo 栈。
5873
+ * 把基础 dbName 与当前 DSL(root app)的 id 拼成最终库名,实现不同应用历史隔离。
5874
+ * 取不到 app id(如尚未加载 DSL)时退回基础 dbName。
5442
5875
  */
5443
- getCodeBlockUndoRedo(codeBlockId) {
5444
- if (!this.state.codeBlockState[codeBlockId]) this.state.codeBlockState[codeBlockId] = new UndoRedo();
5445
- return this.state.codeBlockState[codeBlockId];
5876
+ resolveDbName(dbName, appId) {
5877
+ const resolvedAppId = appId ?? editor_default.get("root")?.id;
5878
+ return resolvedAppId ? `${dbName}-${resolvedAppId}` : dbName;
5446
5879
  }
5447
- /**
5448
- * id 获取(或创建)指定数据源的 UndoRedo 栈。
5449
- */
5450
- getDataSourceUndoRedo(dataSourceId) {
5451
- if (!this.state.dataSourceState[dataSourceId]) this.state.dataSourceState[dataSourceId] = new UndoRedo();
5452
- return this.state.dataSourceState[dataSourceId];
5880
+ setCanUndoRedo() {
5881
+ const undoRedo = this.getUndoRedo();
5882
+ this.state.canRedo = undoRedo?.canRedo() || false;
5883
+ this.state.canUndo = undoRedo ? undoRedo.getCursor() > undoFloor(undoRedo) : false;
5453
5884
  }
5454
5885
  };
5455
5886
  var history_default = new History();
5456
5887
  //#endregion
5457
- //#region packages/editor/src/utils/config.ts
5458
- var $TMAGIC_EDITOR = {};
5459
- var setEditorConfig = (option) => {
5460
- $TMAGIC_EDITOR = option;
5461
- };
5462
- var getEditorConfig = (key) => $TMAGIC_EDITOR[key];
5463
- //#endregion
5464
5888
  //#region packages/editor/src/services/storage.ts
5465
5889
  var Protocol = /* @__PURE__ */ function(Protocol) {
5466
5890
  Protocol["OBJECT"] = "object";
@@ -5653,12 +6077,26 @@
5653
6077
  }
5654
6078
  return (Math.min(parentHeight, wrapperHeightDeal) - height) / 2;
5655
6079
  };
6080
+ var removeConflictPosition = (style, primary, secondary) => {
6081
+ const isInvalid = (value) => value === "" || value === 0 || value === void 0 || value === null;
6082
+ if (!(primary in style) || !(secondary in style)) return;
6083
+ const primaryValue = style[primary];
6084
+ const secondaryValue = style[secondary];
6085
+ const primaryInvalid = isInvalid(primaryValue);
6086
+ const secondaryInvalid = isInvalid(secondaryValue);
6087
+ if (primaryInvalid && !secondaryInvalid) delete style[primary];
6088
+ else if (secondaryInvalid && !primaryInvalid) delete style[secondary];
6089
+ else if (primaryInvalid && secondaryInvalid) if (secondaryValue === 0 && primaryValue !== 0) delete style[primary];
6090
+ else delete style[secondary];
6091
+ };
5656
6092
  var getInitPositionStyle = (style = {}, layout) => {
5657
6093
  if (layout === Layout.ABSOLUTE) {
5658
6094
  const newStyle = {
5659
6095
  ...style,
5660
6096
  position: "absolute"
5661
6097
  };
6098
+ removeConflictPosition(newStyle, "left", "right");
6099
+ removeConflictPosition(newStyle, "top", "bottom");
5662
6100
  if (typeof newStyle.left === "undefined" && typeof newStyle.right === "undefined") newStyle.left = 0;
5663
6101
  return newStyle;
5664
6102
  }
@@ -5683,58 +6121,63 @@
5683
6121
  }
5684
6122
  return node;
5685
6123
  };
5686
- var change2Fixed = (node, root) => {
5687
- const style = { ...node.style || {} };
5688
- const path = (0, _tmagic_utils.getNodePath)(node.id, root.items);
5689
- const offset = {
5690
- left: 0,
5691
- top: 0
5692
- };
5693
- if (!node.style?.right && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) for (const value of path) {
5694
- if (value.style?.right || !(0, _tmagic_utils.isNumber)(value.style?.left || 0)) {
5695
- offset.left = 0;
5696
- break;
5697
- }
5698
- offset.left = offset.left + Number(value.style?.left || 0);
6124
+ var hasPositionValue = (style, key) => typeof style?.[key] !== "undefined" && style?.[key] !== "";
6125
+ /**
6126
+ * 沿节点路径累加(或抵消)某一方向上的定位偏移量
6127
+ * 当路径上的祖先使用了反方向定位(例如计算 left 时祖先用了 right)或非数值定位时,
6128
+ * 无法简单地通过求和换算坐标,此时返回 0 表示放弃补偿、保持原值
6129
+ * @param path 从根到目标节点的路径(含节点自身)
6130
+ * @param dir 需要累加的定位方向
6131
+ * @param opposite 反方向定位属性
6132
+ * @param sign change2Fixed 1(累加祖先偏移),Fixed2Other -1(抵消祖先偏移)
6133
+ * @param init 偏移量初始值
6134
+ */
6135
+ var accumulatePositionOffset = (path, dir, opposite, sign, init = 0) => {
6136
+ let offset = init;
6137
+ for (const value of path) {
6138
+ if (hasPositionValue(value.style, opposite) || !(0, _tmagic_utils.isNumber)(value.style?.[dir] || 0)) return 0;
6139
+ offset = offset + sign * Number(value.style?.[dir] || 0);
5699
6140
  }
5700
- if (!node.style?.bottom && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) for (const value of path) {
5701
- if (value.style?.bottom || !(0, _tmagic_utils.isNumber)(value.style?.top || 0)) {
5702
- offset.top = 0;
5703
- break;
5704
- }
5705
- offset.top = offset.top + Number(value.style?.top || 0);
6141
+ return offset;
6142
+ };
6143
+ var change2Fixed = (node, path) => {
6144
+ const style = { ...node.style || {} };
6145
+ if (!hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) {
6146
+ const left = accumulatePositionOffset(path, "left", "right", 1);
6147
+ if (left) style.left = left;
6148
+ } else if (hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.right || 0)) {
6149
+ const right = accumulatePositionOffset(path, "right", "left", 1);
6150
+ if (right) style.right = right;
6151
+ }
6152
+ if (!hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) {
6153
+ const top = accumulatePositionOffset(path, "top", "bottom", 1);
6154
+ if (top) style.top = top;
6155
+ } else if (hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.bottom || 0)) {
6156
+ const bottom = accumulatePositionOffset(path, "bottom", "top", 1);
6157
+ if (bottom) style.bottom = bottom;
5706
6158
  }
5707
- if (offset.left) style.left = offset.left;
5708
- if (offset.top) style.top = offset.top;
5709
6159
  return style;
5710
6160
  };
5711
- var Fixed2Other = async (node, root, getLayout) => {
5712
- const path = (0, _tmagic_utils.getNodePath)(node.id, root.items);
6161
+ var Fixed2Other = async (node, path, getLayout) => {
5713
6162
  const cur = path.pop();
5714
6163
  const offset = {
5715
- left: cur?.style?.left || 0,
5716
- top: cur?.style?.top || 0
6164
+ left: 0,
6165
+ top: 0,
6166
+ right: 0,
6167
+ bottom: 0
5717
6168
  };
5718
- if (!node.style?.right && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) for (const value of path) {
5719
- if (value.style?.right || !(0, _tmagic_utils.isNumber)(value.style?.left || 0)) {
5720
- offset.left = 0;
5721
- break;
5722
- }
5723
- offset.left = offset.left - Number(value.style?.left || 0);
5724
- }
5725
- if (!node.style?.bottom && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) for (const value of path) {
5726
- if (value.style?.bottom || !(0, _tmagic_utils.isNumber)(value.style?.top || 0)) {
5727
- offset.top = 0;
5728
- break;
5729
- }
5730
- offset.top = offset.top - Number(value.style?.top || 0);
5731
- }
6169
+ if (!hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) offset.left = accumulatePositionOffset(path, "left", "right", -1, Number(cur?.style?.left || 0));
6170
+ else if (hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.right || 0)) offset.right = accumulatePositionOffset(path, "right", "left", -1, Number(cur?.style?.right || 0));
6171
+ if (!hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) offset.top = accumulatePositionOffset(path, "top", "bottom", -1, Number(cur?.style?.top || 0));
6172
+ else if (hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.bottom || 0)) offset.bottom = accumulatePositionOffset(path, "bottom", "top", -1, Number(cur?.style?.bottom || 0));
5732
6173
  const style = node.style || {};
5733
6174
  const parent = path.pop();
5734
6175
  if (!parent) return getRelativeStyle(style);
5735
6176
  if (await getLayout(parent) !== Layout.RELATIVE) {
5736
6177
  if (offset.left) style.left = offset.left;
5737
6178
  if (offset.top) style.top = offset.top;
6179
+ if (offset.right) style.right = offset.right;
6180
+ if (offset.bottom) style.bottom = offset.bottom;
5738
6181
  return {
5739
6182
  ...style,
5740
6183
  position: "absolute"
@@ -5766,11 +6209,11 @@
5766
6209
  };
5767
6210
  var fixNodePosition = (config, parent, stage) => {
5768
6211
  if (config.style?.position !== "absolute") return config.style;
5769
- return {
5770
- ...config.style || {},
5771
- top: getMiddleTop(config, parent, stage),
5772
- left: fixNodeLeft(config, parent, stage?.renderer?.contentWindow?.document)
5773
- };
6212
+ const style = { ...config.style || {} };
6213
+ const baseStyle = config.style || {};
6214
+ if ("left" in baseStyle && !("right" in baseStyle)) style.left = fixNodeLeft(config, parent, stage?.renderer?.contentWindow?.document);
6215
+ if ("top" in baseStyle && !("bottom" in baseStyle)) style.top = getMiddleTop(config, parent, stage);
6216
+ return style;
5774
6217
  };
5775
6218
  var serializeConfig = (config) => (0, serialize_javascript.default)(config, {
5776
6219
  space: 2,
@@ -5847,13 +6290,14 @@
5847
6290
  var resolveSelectedNode = (config, getNodeInfoFn, rootId) => {
5848
6291
  const id = typeof config === "string" || typeof config === "number" ? config : config.id;
5849
6292
  if (!id) throw new Error("没有ID,无法选中");
5850
- const { node, parent, page } = getNodeInfoFn(id);
6293
+ const { node, parent, page, path } = getNodeInfoFn(id);
5851
6294
  if (!node) throw new Error("获取不到组件信息");
5852
6295
  if (node.id === rootId) throw new Error("不能选根节点");
5853
6296
  return {
5854
6297
  node,
5855
6298
  parent,
5856
- page
6299
+ page,
6300
+ path
5857
6301
  };
5858
6302
  };
5859
6303
  /**
@@ -5865,11 +6309,11 @@
5865
6309
  * @param getLayoutFn 获取父节点布局方式的回调函数
5866
6310
  * @returns 处理后的节点配置(深拷贝)
5867
6311
  */
5868
- var toggleFixedPosition = async (dist, src, root, getLayoutFn) => {
6312
+ var toggleFixedPosition = async (dist, src, path, getLayoutFn) => {
5869
6313
  const newConfig = cloneDeep$1(dist);
5870
6314
  if (!(0, _tmagic_utils.isPop)(src) && newConfig.style?.position) {
5871
- if ((0, _tmagic_stage.isFixed)(newConfig.style) && !(0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = change2Fixed(newConfig, root);
5872
- else if (!(0, _tmagic_stage.isFixed)(newConfig.style) && (0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = await Fixed2Other(newConfig, root, getLayoutFn);
6315
+ if ((0, _tmagic_stage.isFixed)(newConfig.style) && !(0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = change2Fixed(newConfig, path);
6316
+ else if (!(0, _tmagic_stage.isFixed)(newConfig.style) && (0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = await Fixed2Other(newConfig, path, getLayoutFn);
5873
6317
  }
5874
6318
  return newConfig;
5875
6319
  };
@@ -6010,6 +6454,40 @@
6010
6454
  aborted: false
6011
6455
  };
6012
6456
  };
6457
+ /**
6458
+ * 给「回滚」生成的新 step 用的简短描述生成器。
6459
+ * 与 UI 层 `describePageStep` 同义,但避免 service 反向依赖 layouts/,故放在此工具函数中。
6460
+ */
6461
+ var describeStepForRevert = (step) => {
6462
+ const items = step.diff ?? [];
6463
+ const withId = (node, label) => {
6464
+ const id = node?.id;
6465
+ if (id === void 0 || id === null || `${id}` === "") return label;
6466
+ return label ? `${label}(id: ${id})` : `id: ${id}`;
6467
+ };
6468
+ switch (step.opType) {
6469
+ case "add": {
6470
+ const count = items.length;
6471
+ const node = items[0]?.newSchema;
6472
+ const label = node?.name || node?.type || "";
6473
+ return `撤回新增 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ""}`;
6474
+ }
6475
+ case "remove": {
6476
+ const count = items.length;
6477
+ const node = items[0]?.oldSchema;
6478
+ const label = node?.name || node?.type || "";
6479
+ return `还原已删除的 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ""}`;
6480
+ }
6481
+ default:
6482
+ if (items.length === 1) {
6483
+ const { newSchema, oldSchema, changeRecords } = items[0];
6484
+ const target = withId(newSchema || oldSchema, newSchema?.name || newSchema?.type || oldSchema?.name || oldSchema?.type || "");
6485
+ const propPath = changeRecords?.[0]?.propPath;
6486
+ return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
6487
+ }
6488
+ return `还原 ${items.length} 个节点的修改`;
6489
+ }
6490
+ };
6013
6491
  //#endregion
6014
6492
  //#region packages/editor/src/utils/operator.ts
6015
6493
  /**
@@ -6078,35 +6556,15 @@
6078
6556
  //#endregion
6079
6557
  //#region packages/editor/src/services/editor.ts
6080
6558
  /**
6081
- * 给「回滚」生成的新 step 用的简短描述生成器。
6082
- * UI `describePageStep` 同义,但避免 service 反向依赖 layouts/,故在此本地实现。
6559
+ * 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
6560
+ * `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
6561
+ * 缺省(未传 / 空数组)才退化为整节点替换。
6083
6562
  */
6084
- var describeStepForRevert = (step) => {
6085
- switch (step.opType) {
6086
- case "add": {
6087
- const count = step.nodes?.length ?? 0;
6088
- const node = step.nodes?.[0];
6089
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
6090
- return `撤回新增 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
6091
- }
6092
- case "remove": {
6093
- const count = step.removedItems?.length ?? 0;
6094
- const node = step.removedItems?.[0]?.node;
6095
- const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
6096
- return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
6097
- }
6098
- default: {
6099
- const items = step.updatedItems ?? [];
6100
- if (items.length === 1) {
6101
- const { newNode, oldNode, changeRecords } = items[0];
6102
- const target = newNode?.name || newNode?.type || oldNode?.name || oldNode?.type || `${newNode?.id ?? ""}`;
6103
- const propPath = changeRecords?.[0]?.propPath;
6104
- return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
6105
- }
6106
- return `还原 ${items.length} 个节点的修改`;
6107
- }
6108
- }
6109
- };
6563
+ var buildUpdateDiff = (items) => items.map(({ oldNode, newNode, changeRecords }) => ({
6564
+ oldSchema: oldNode,
6565
+ newSchema: newNode,
6566
+ ...changeRecords?.length ? { changeRecords } : {}
6567
+ }));
6110
6568
  var Editor = class extends BaseService {
6111
6569
  state = (0, vue.reactive)({
6112
6570
  root: null,
@@ -6124,6 +6582,12 @@
6124
6582
  alwaysMultiSelect: false
6125
6583
  });
6126
6584
  selectionBeforeOp = null;
6585
+ /**
6586
+ * 最近一次 pushOpHistory 写入的历史记录 uuid。
6587
+ * 供 *AndGetHistoryId 系列方法在调用普通操作后取回本次产生的历史记录 id;
6588
+ * 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
6589
+ */
6590
+ lastPushedHistoryId = null;
6127
6591
  constructor() {
6128
6592
  super(canUsePluginMethods.async.map((methodName) => ({
6129
6593
  name: methodName,
@@ -6138,8 +6602,9 @@
6138
6602
  * 设置当前指点节点配置
6139
6603
  * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
6140
6604
  * @param value MNode
6605
+ * @param options.historySource 设置 root 时,本次变更写入历史记录的「操作来源」(仅 name === 'root' 时生效)
6141
6606
  */
6142
- set(name, value) {
6607
+ set(name, value, options = {}) {
6143
6608
  const preValue = this.state[name];
6144
6609
  this.state[name] = value;
6145
6610
  if (name === "nodes" && Array.isArray(value)) this.set("node", value[0]);
@@ -6150,12 +6615,19 @@
6150
6615
  this.state.pageLength = getPageList(app).length || 0;
6151
6616
  this.state.pageFragmentLength = getPageFragmentList(app).length || 0;
6152
6617
  this.state.stageLoading = this.state.pageLength !== 0;
6618
+ if (preValue && !isEmpty(preValue)) this.pushRootDiffHistory(preValue, app, options.historySource);
6619
+ else app.items?.forEach((pageNode) => {
6620
+ if (pageNode?.id !== void 0 && !history_default.getPageMarker(pageNode.id)) history_default.setPageMarker(pageNode.id, {
6621
+ name: pageNode.name,
6622
+ source: options.historySource
6623
+ });
6624
+ });
6153
6625
  } else {
6154
6626
  this.state.pageLength = 0;
6155
6627
  this.state.pageFragmentLength = 0;
6156
6628
  this.state.stageLoading = false;
6157
6629
  }
6158
- this.emit("root-change", value, preValue);
6630
+ this.emit("root-change", value, preValue, options);
6159
6631
  }
6160
6632
  }
6161
6633
  /**
@@ -6175,7 +6647,25 @@
6175
6647
  getNodeInfo(id, raw = true) {
6176
6648
  let root = this.get("root");
6177
6649
  if (raw) root = (0, vue.toRaw)(root);
6178
- return (0, _tmagic_utils.getNodeInfo)(id, root);
6650
+ if (!root) return {
6651
+ node: null,
6652
+ parent: null,
6653
+ page: null,
6654
+ path: []
6655
+ };
6656
+ if (id === root.id) return {
6657
+ node: root,
6658
+ parent: null,
6659
+ page: null,
6660
+ path: []
6661
+ };
6662
+ const pageIdStr = `${this.get("page")?.id || ""}`;
6663
+ const currentPageNode = root.items?.find((item) => `${item.id}` === pageIdStr);
6664
+ if (currentPageNode && `${id}` !== pageIdStr) {
6665
+ const info = (0, _tmagic_utils.getNodeInfo)(id, currentPageNode);
6666
+ if (info.node) return info;
6667
+ }
6668
+ return (0, _tmagic_utils.getNodeInfo)(id, root, currentPageNode);
6179
6669
  }
6180
6670
  /**
6181
6671
  * 根据ID获取指点节点配置
@@ -6368,23 +6858,26 @@
6368
6858
  }
6369
6859
  if (!((0, _tmagic_utils.isPage)(newNodes[0]) || (0, _tmagic_utils.isPageFragment)(newNodes[0]))) {
6370
6860
  const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
6371
- if (!doNotPushHistory) this.pushOpHistory("add", {
6372
- extra: {
6373
- nodes: newNodes.map((n) => cloneDeep$1((0, vue.toRaw)(n))),
6374
- parentId: (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id,
6375
- indexMap: Object.fromEntries(newNodes.map((n) => {
6861
+ if (!doNotPushHistory) {
6862
+ const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id;
6863
+ this.pushOpHistory("add", {
6864
+ diff: newNodes.map((n) => {
6376
6865
  const p = this.getParentById(n.id, false);
6377
- return [n.id, p ? getNodeIndex(n.id, p) : -1];
6378
- }))
6379
- },
6380
- pageData: {
6381
- name: pageForOp?.name || "",
6382
- id: pageForOp.id
6383
- },
6384
- historyDescription,
6385
- source: historySource
6386
- });
6387
- else this.selectionBeforeOp = null;
6866
+ const idx = p ? getNodeIndex(n.id, p) : -1;
6867
+ return {
6868
+ newSchema: cloneDeep$1((0, vue.toRaw)(n)),
6869
+ parentId,
6870
+ index: typeof idx === "number" ? idx : -1
6871
+ };
6872
+ }),
6873
+ pageData: {
6874
+ name: pageForOp?.name || "",
6875
+ id: pageForOp.id
6876
+ },
6877
+ historyDescription,
6878
+ source: historySource
6879
+ });
6880
+ } else this.selectionBeforeOp = null;
6388
6881
  }
6389
6882
  this.emit("add", newNodes);
6390
6883
  return Array.isArray(addNode) ? newNodes : newNodes[0];
@@ -6464,7 +6957,7 @@
6464
6957
  };
6465
6958
  const idx = getNodeIndex(curNode.id, parent);
6466
6959
  removedItems.push({
6467
- node: cloneDeep$1((0, vue.toRaw)(curNode)),
6960
+ oldSchema: cloneDeep$1((0, vue.toRaw)(curNode)),
6468
6961
  parentId: parent.id,
6469
6962
  index: typeof idx === "number" ? idx : -1
6470
6963
  });
@@ -6475,7 +6968,7 @@
6475
6968
  doNotSwitchPage
6476
6969
  })));
6477
6970
  if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", {
6478
- extra: { removedItems },
6971
+ diff: removedItems,
6479
6972
  pageData: pageForOp,
6480
6973
  historyDescription,
6481
6974
  source: historySource
@@ -6483,18 +6976,17 @@
6483
6976
  else this.selectionBeforeOp = null;
6484
6977
  this.emit("remove", nodes);
6485
6978
  }
6486
- async doUpdate(config, { changeRecords = [] } = {}) {
6487
- const root = this.get("root");
6488
- if (!root) throw new Error("root为空");
6979
+ async doUpdate(config, { changeRecords = [], historySource } = {}) {
6980
+ if (!this.get("root")) throw new Error("root为空");
6489
6981
  if (!config?.id) throw new Error("没有配置或者配置缺少id值");
6490
6982
  const info = this.getNodeInfo(config.id, false);
6491
6983
  if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
6492
6984
  const node = (0, vue.toRaw)(info.node);
6493
- let newConfig = await toggleFixedPosition((0, vue.toRaw)(config), node, root, this.getLayout);
6985
+ let newConfig = await toggleFixedPosition((0, vue.toRaw)(config), node, info.path, this.getLayout);
6494
6986
  newConfig = mergeWith(cloneDeep$1(node), newConfig, editorNodeMergeCustomizer);
6495
6987
  if (!newConfig.type) throw new Error("配置缺少type值");
6496
6988
  if (newConfig.type === _tmagic_core.NodeType.ROOT) {
6497
- this.set("root", newConfig);
6989
+ this.set("root", newConfig, { historySource });
6498
6990
  return {
6499
6991
  oldNode: node,
6500
6992
  newNode: newConfig,
@@ -6544,17 +7036,20 @@
6544
7036
  const nodes = Array.isArray(config) ? config : [config];
6545
7037
  const updateData = await Promise.all(nodes.map((node, index) => {
6546
7038
  const recordsForNode = changeRecordList ? changeRecordList[index] ?? [] : changeRecords ?? [];
6547
- return this.doUpdate(node, { changeRecords: recordsForNode });
7039
+ return this.doUpdate(node, {
7040
+ changeRecords: recordsForNode,
7041
+ historySource
7042
+ });
6548
7043
  }));
6549
7044
  if (updateData[0].oldNode?.type !== _tmagic_core.NodeType.ROOT) {
6550
7045
  if (this.get("nodes").length) if (!doNotPushHistory) {
6551
7046
  const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
6552
7047
  this.pushOpHistory("update", {
6553
- extra: { updatedItems: updateData.map((d) => ({
7048
+ diff: buildUpdateDiff(updateData.map((d) => ({
6554
7049
  oldNode: cloneDeep$1(d.oldNode),
6555
- newNode: cloneDeep$1((0, vue.toRaw)(d.newNode)),
7050
+ newNode: cloneDeep$1(d.newNode),
6556
7051
  changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
6557
- })) },
7052
+ }))),
6558
7053
  pageData: {
6559
7054
  name: pageForOp?.name || "",
6560
7055
  id: pageForOp.id
@@ -6717,10 +7212,10 @@
6717
7212
  if (!doNotPushHistory) {
6718
7213
  const pageForOp = this.getNodeInfo(node.id, false).page;
6719
7214
  this.pushOpHistory("update", {
6720
- extra: { updatedItems: [{
7215
+ diff: buildUpdateDiff([{
6721
7216
  oldNode: oldParent,
6722
7217
  newNode: cloneDeep$1((0, vue.toRaw)(parent))
6723
- }] },
7218
+ }]),
6724
7219
  pageData: {
6725
7220
  name: pageForOp?.name || "",
6726
7221
  id: pageForOp.id
@@ -6794,7 +7289,7 @@
6794
7289
  id: target.id
6795
7290
  };
6796
7291
  this.pushOpHistory("update", {
6797
- extra: { updatedItems },
7292
+ diff: buildUpdateDiff(updatedItems),
6798
7293
  pageData: historyPage,
6799
7294
  historyDescription,
6800
7295
  source: historySource
@@ -6845,7 +7340,7 @@
6845
7340
  if (!doNotPushHistory) {
6846
7341
  const pageForOp = this.getNodeInfo(configs[0].id, false).page;
6847
7342
  this.pushOpHistory("update", {
6848
- extra: { updatedItems },
7343
+ diff: buildUpdateDiff(updatedItems),
6849
7344
  pageData: {
6850
7345
  name: pageForOp?.name || "",
6851
7346
  id: pageForOp.id
@@ -6861,6 +7356,64 @@
6861
7356
  });
6862
7357
  }
6863
7358
  /**
7359
+ * 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
7360
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link StepValue.uuid}),
7361
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
7362
+ *
7363
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时 historyIds 为 `[]`。
7364
+ */
7365
+ /** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7366
+ async addAndGetHistoryId(addNode, parent, options = {}) {
7367
+ this.lastPushedHistoryId = null;
7368
+ return {
7369
+ result: await this.add(addNode, parent, options),
7370
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7371
+ };
7372
+ }
7373
+ /** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7374
+ async removeAndGetHistoryId(nodeOrNodeList, options = {}) {
7375
+ this.lastPushedHistoryId = null;
7376
+ await this.remove(nodeOrNodeList, options);
7377
+ return {
7378
+ result: void 0,
7379
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7380
+ };
7381
+ }
7382
+ /** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7383
+ async updateAndGetHistoryId(config, data = {}) {
7384
+ this.lastPushedHistoryId = null;
7385
+ return {
7386
+ result: await this.update(config, data),
7387
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7388
+ };
7389
+ }
7390
+ /** 等价于 {@link moveLayer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7391
+ async moveLayerAndGetHistoryId(offset, options = {}) {
7392
+ this.lastPushedHistoryId = null;
7393
+ await this.moveLayer(offset, options);
7394
+ return {
7395
+ result: void 0,
7396
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7397
+ };
7398
+ }
7399
+ /** 等价于 {@link moveToContainer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7400
+ async moveToContainerAndGetHistoryId(config, targetId, options = {}) {
7401
+ this.lastPushedHistoryId = null;
7402
+ return {
7403
+ result: await this.moveToContainer(config, targetId, options),
7404
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7405
+ };
7406
+ }
7407
+ /** 等价于 {@link dragTo},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7408
+ async dragToAndGetHistoryId(config, targetParent, targetIndex, options = {}) {
7409
+ this.lastPushedHistoryId = null;
7410
+ await this.dragTo(config, targetParent, targetIndex, options);
7411
+ return {
7412
+ result: void 0,
7413
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7414
+ };
7415
+ }
7416
+ /**
6864
7417
  * 撤销当前操作
6865
7418
  * @returns 被撤销的操作
6866
7419
  */
@@ -6898,9 +7451,10 @@
6898
7451
  const entry = history_default.getPageStepList()[index];
6899
7452
  if (!entry?.applied) return null;
6900
7453
  const { step } = entry;
7454
+ if (step.opType === "initial") return null;
6901
7455
  if (!this.get("root")) return null;
6902
7456
  if (step.opType === "update") {
6903
- const items = step.updatedItems ?? [];
7457
+ const items = step.diff ?? [];
6904
7458
  if (!items.length || !items.every((item) => item.changeRecords?.length)) return null;
6905
7459
  }
6906
7460
  let revertedStep = null;
@@ -6917,24 +7471,26 @@
6917
7471
  };
6918
7472
  try {
6919
7473
  switch (step.opType) {
6920
- case "add": {
6921
- const nodes = step.nodes ?? [];
6922
- for (const n of nodes) {
6923
- const existing = this.getNodeById(n.id, false);
7474
+ case "add":
7475
+ for (const { newSchema } of step.diff ?? []) {
7476
+ if (!newSchema) continue;
7477
+ const existing = this.getNodeById(newSchema.id, false);
6924
7478
  if (existing) await this.remove(existing, opts);
6925
7479
  }
6926
7480
  break;
6927
- }
6928
7481
  case "remove": {
6929
- const sorted = [...step.removedItems ?? []].sort((a, b) => a.index - b.index);
6930
- for (const { node, parentId } of sorted) {
7482
+ const sorted = [...step.diff ?? []].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7483
+ for (const { oldSchema, parentId } of sorted) {
7484
+ if (!oldSchema || parentId === void 0) continue;
6931
7485
  const parent = this.getNodeById(parentId, false);
6932
- if (parent) await this.add([cloneDeep$1(node)], parent, opts);
7486
+ if (parent) await this.add([cloneDeep$1(oldSchema)], parent, opts);
6933
7487
  }
6934
7488
  break;
6935
7489
  }
6936
7490
  case "update": {
6937
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
7491
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
7492
+ const oldNode = oldSchema;
7493
+ const newNode = newSchema;
6938
7494
  if (changeRecords?.length) {
6939
7495
  const patch = {
6940
7496
  id: newNode.id,
@@ -6964,6 +7520,21 @@
6964
7520
  return revertedStep;
6965
7521
  }
6966
7522
  /**
7523
+ * 通过历史记录 uuid 回滚当前页面的历史步骤,语义与 {@link revertPageStep} 完全一致,
7524
+ * 仅入参从 index 改为 uuid 列表({@link StepValue.uuid})。按数组顺序依次回滚,
7525
+ * 返回与入参同序的结果列表(某项失败时为 `null`)。
7526
+ *
7527
+ * @param uuids 目标历史记录的 uuid 列表,通常由 *AndGetHistoryId 方法返回的 `historyIds`
7528
+ */
7529
+ async revertPageStepById(uuids) {
7530
+ const results = [];
7531
+ for (const uuid of uuids) {
7532
+ const index = history_default.getPageStepIndexByUuid(uuid);
7533
+ results.push(index < 0 ? null : await this.revertPageStep(index));
7534
+ }
7535
+ return results;
7536
+ }
7537
+ /**
6967
7538
  * 跳转当前页面历史栈到指定游标位置。
6968
7539
  *
6969
7540
  * `targetCursor` 与 `UndoRedo.getCursor()` 同义:表示"已应用步骤数量",
@@ -7039,19 +7610,83 @@
7039
7610
  if (this.selectionBeforeOp) return;
7040
7611
  this.selectionBeforeOp = this.get("nodes").map((n) => n.id);
7041
7612
  }
7042
- pushOpHistory(opType, { extra, pageData, historyDescription, source }) {
7613
+ /**
7614
+ * 比较「上一次 root」与「新 root」的页面 / 页面片,按页面粒度把整体替换拆成历史记录:
7615
+ * - 新旧都存在且内容变化的页面 → 一条 `update`(整页快照替换,无 changeRecords);
7616
+ * - 仅新 root 存在的页面 → 一条 `add`;
7617
+ * - 仅旧 root 存在的页面 → 一条 `remove`。
7618
+ *
7619
+ * 每条记录落到对应页面自己的历史栈(与普通节点操作一致),并标记来源 `source`。
7620
+ * 内容未变化的页面不产生记录,避免重复设置相同 DSL 时产生噪声。
7621
+ */
7622
+ pushRootDiffHistory(preRoot, nextRoot, source) {
7623
+ const prevPages = preRoot?.items || [];
7624
+ const nextPages = nextRoot?.items || [];
7625
+ const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
7626
+ const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
7627
+ const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
7628
+ nextPages.forEach((nextPage) => {
7629
+ const prevPage = prevMap.get(`${nextPage.id}`);
7630
+ if (!prevPage) this.pushPageDiffStep("add", nextPage, {
7631
+ newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
7632
+ parentId: nextRoot.id,
7633
+ index: indexInItems(nextRoot, nextPage.id)
7634
+ }, source);
7635
+ else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
7636
+ oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
7637
+ newSchema: cloneDeep$1((0, vue.toRaw)(nextPage))
7638
+ }, source);
7639
+ });
7640
+ prevPages.forEach((prevPage) => {
7641
+ if (!nextMap.has(`${prevPage.id}`)) this.pushPageDiffStep("remove", prevPage, {
7642
+ oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
7643
+ parentId: preRoot.id,
7644
+ index: indexInItems(preRoot, prevPage.id)
7645
+ }, source);
7646
+ });
7647
+ }
7648
+ /**
7649
+ * 构造一条页面级「set root」历史记录(不携带选区 / modifiedNodeIds 上下文)并落到该页面自己的栈。
7650
+ *
7651
+ * 连续 set root 替换:若该页栈最新一条已是**同来源**的 set root 记录({@link StepValue.rootStep} 且 `source` 相同),
7652
+ * 则用本次记录**替换**它而非新增,避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录;
7653
+ * 来源不同则照常新增(initial 基线不是 rootStep,不在此列)。
7654
+ */
7655
+ pushPageDiffStep(opType, page, diffItem, source) {
7656
+ const step = {
7657
+ uuid: (0, _tmagic_utils.guid)(),
7658
+ data: {
7659
+ name: page.name || "",
7660
+ id: page.id
7661
+ },
7662
+ opType,
7663
+ selectedBefore: [],
7664
+ selectedAfter: [],
7665
+ modifiedNodeIds: /* @__PURE__ */ new Map(),
7666
+ diff: [diffItem],
7667
+ rootStep: true
7668
+ };
7669
+ if (source) step.source = source;
7670
+ const top = history_default.getCurrentPageStep(page.id);
7671
+ if (top?.rootStep && top.source === source) history_default.replaceCurrentPageStep(step, page.id);
7672
+ else history_default.push(step, page.id);
7673
+ }
7674
+ pushOpHistory(opType, { diff, pageData, historyDescription, source }) {
7043
7675
  const step = {
7676
+ uuid: (0, _tmagic_utils.guid)(),
7044
7677
  data: pageData,
7045
7678
  opType,
7046
7679
  selectedBefore: this.selectionBeforeOp ?? [],
7047
7680
  selectedAfter: this.get("nodes").map((n) => n.id),
7048
7681
  modifiedNodeIds: new Map(this.get("modifiedNodeIds")),
7049
- ...extra
7682
+ diff
7050
7683
  };
7051
7684
  if (historyDescription) step.historyDescription = historyDescription;
7052
7685
  if (source) step.source = source;
7053
- history_default.push(step, pageData.id);
7686
+ const historyId = history_default.push(step, pageData.id) ? step.uuid : null;
7687
+ this.lastPushedHistoryId = historyId;
7054
7688
  this.selectionBeforeOp = null;
7689
+ return historyId;
7055
7690
  }
7056
7691
  /**
7057
7692
  * 应用历史操作(撤销 / 重做)
@@ -7066,6 +7701,7 @@
7066
7701
  * @param reverse true = 撤销,false = 重做
7067
7702
  */
7068
7703
  async applyHistoryOp(step, reverse) {
7704
+ if (step.opType === "initial") return;
7069
7705
  const root = this.get("root");
7070
7706
  const stage = this.get("stage");
7071
7707
  if (!root) return;
@@ -7076,56 +7712,59 @@
7076
7712
  };
7077
7713
  switch (step.opType) {
7078
7714
  case "add": {
7079
- const nodes = step.nodes ?? [];
7080
- if (reverse) for (const n of nodes) {
7081
- const existing = this.getNodeById(n.id, false);
7715
+ const items = step.diff ?? [];
7716
+ if (reverse) for (const { newSchema } of items) {
7717
+ if (!newSchema) continue;
7718
+ const existing = this.getNodeById(newSchema.id, false);
7082
7719
  if (existing) await this.remove(existing, commonOpts);
7083
7720
  }
7084
7721
  else {
7085
- const parent = this.getNodeById(step.parentId, false);
7086
- if (parent) {
7087
- const sorted = [...nodes].sort((a, b) => (step.indexMap?.[a.id] ?? 0) - (step.indexMap?.[b.id] ?? 0));
7088
- for (const n of sorted) {
7089
- const idx = step.indexMap?.[n.id];
7090
- if (parent.items) {
7091
- if (typeof idx === "number" && idx >= 0 && idx < parent.items.length) parent.items.splice(idx, 0, cloneDeep$1(n));
7092
- else parent.items.push(cloneDeep$1(n));
7093
- await stage?.add({
7094
- config: cloneDeep$1(n),
7095
- parent: cloneDeep$1(parent),
7096
- parentId: parent.id,
7097
- root: cloneDeep$1(root)
7098
- });
7099
- }
7722
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7723
+ for (const { newSchema, parentId, index } of sorted) {
7724
+ if (!newSchema || parentId === void 0) continue;
7725
+ const parent = this.getNodeById(parentId, false);
7726
+ if (parent?.items) {
7727
+ if (typeof index === "number" && index >= 0 && index < parent.items.length) parent.items.splice(index, 0, cloneDeep$1(newSchema));
7728
+ else parent.items.push(cloneDeep$1(newSchema));
7729
+ await stage?.add({
7730
+ config: cloneDeep$1(newSchema),
7731
+ parent: cloneDeep$1(parent),
7732
+ parentId: parent.id,
7733
+ root: cloneDeep$1(root)
7734
+ });
7100
7735
  }
7101
7736
  }
7102
7737
  }
7103
7738
  break;
7104
7739
  }
7105
7740
  case "remove": {
7106
- const items = step.removedItems ?? [];
7741
+ const items = step.diff ?? [];
7107
7742
  if (reverse) {
7108
- const sorted = [...items].sort((a, b) => a.index - b.index);
7109
- for (const { node, parentId, index } of sorted) {
7743
+ const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
7744
+ for (const { oldSchema, parentId, index } of sorted) {
7745
+ if (!oldSchema || parentId === void 0) continue;
7110
7746
  const parent = this.getNodeById(parentId, false);
7111
7747
  if (parent?.items) {
7112
- parent.items.splice(index, 0, cloneDeep$1(node));
7748
+ parent.items.splice(index ?? parent.items.length, 0, cloneDeep$1(oldSchema));
7113
7749
  await stage?.add({
7114
- config: cloneDeep$1(node),
7750
+ config: cloneDeep$1(oldSchema),
7115
7751
  parent: cloneDeep$1(parent),
7116
7752
  parentId,
7117
7753
  root: cloneDeep$1(root)
7118
7754
  });
7119
7755
  }
7120
7756
  }
7121
- } else for (const { node } of items) {
7122
- const existing = this.getNodeById(node.id, false);
7757
+ } else for (const { oldSchema } of items) {
7758
+ if (!oldSchema) continue;
7759
+ const existing = this.getNodeById(oldSchema.id, false);
7123
7760
  if (existing) await this.remove(existing, commonOpts);
7124
7761
  }
7125
7762
  break;
7126
7763
  }
7127
7764
  case "update": {
7128
- const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
7765
+ const configs = (step.diff ?? []).filter((item) => item.oldSchema && item.newSchema).map(({ oldSchema, newSchema, changeRecords }) => {
7766
+ const oldNode = oldSchema;
7767
+ const newNode = newSchema;
7129
7768
  if (changeRecords?.length) {
7130
7769
  const sourceForValues = reverse ? oldNode : newNode;
7131
7770
  const patch = {
@@ -7206,6 +7845,7 @@
7206
7845
  showPageListButton: true,
7207
7846
  hideSlideBar: false,
7208
7847
  sideBarItems: [],
7848
+ sideBarActiveTabName: "",
7209
7849
  navMenuRect: {
7210
7850
  left: 0,
7211
7851
  top: 0,
@@ -7238,7 +7878,9 @@
7238
7878
  }
7239
7879
  if (name === "showGuides") mask?.showGuides(value);
7240
7880
  if (name === "showRule") mask?.showRule(value);
7881
+ const preValue = state[name];
7241
7882
  state[name] = value;
7883
+ if (preValue !== value) this.emit("state-change", name, value, preValue);
7242
7884
  }
7243
7885
  get(name) {
7244
7886
  return state[name];
@@ -7872,6 +8514,7 @@
7872
8514
  items: [
7873
8515
  {
7874
8516
  title: "数据定义",
8517
+ status: "fields",
7875
8518
  items: [{
7876
8519
  name: "fields",
7877
8520
  type: "data-source-fields",
@@ -7880,6 +8523,7 @@
7880
8523
  },
7881
8524
  {
7882
8525
  title: "方法定义",
8526
+ status: "methods",
7883
8527
  items: [{
7884
8528
  name: "methods",
7885
8529
  type: "data-source-methods",
@@ -8461,7 +9105,7 @@
8461
9105
  key: 1,
8462
9106
  class: "menu-item-text"
8463
9107
  };
8464
- var _hoisted_2$28 = { class: "el-dropdown-link menubar-menu-button" };
9108
+ var _hoisted_2$30 = { class: "el-dropdown-link menubar-menu-button" };
8465
9109
  var ToolButton_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8466
9110
  name: "MEditorToolButton",
8467
9111
  __name: "ToolButton",
@@ -8526,11 +9170,11 @@
8526
9170
  placement: "bottom-start",
8527
9171
  content: __props.data.tooltip
8528
9172
  }, {
8529
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
9173
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8530
9174
  size: "small",
8531
9175
  link: "",
8532
9176
  disabled: disabled.value
8533
- }, (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
9177
+ }, __props.data.buttonProps || {}), (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
8534
9178
  name: "icon",
8535
9179
  fn: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: __props.data.icon }, null, 8, ["icon"])]),
8536
9180
  key: "0"
@@ -8538,15 +9182,15 @@
8538
9182
  name: "default",
8539
9183
  fn: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1)]),
8540
9184
  key: "1"
8541
- } : void 0]), 1032, ["disabled"])]),
9185
+ } : void 0]), 1040, ["disabled"])]),
8542
9186
  _: 1
8543
- }, 8, ["content"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
9187
+ }, 8, ["content"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8544
9188
  key: 1,
8545
9189
  size: "small",
8546
9190
  link: "",
8547
9191
  disabled: disabled.value,
8548
9192
  title: __props.data.text
8549
- }, (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
9193
+ }, __props.data.buttonProps || {}), (0, vue.createSlots)({ _: 2 }, [__props.data.icon ? {
8550
9194
  name: "icon",
8551
9195
  fn: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: __props.data.icon }, null, 8, ["icon"])]),
8552
9196
  key: "0"
@@ -8554,7 +9198,7 @@
8554
9198
  name: "default",
8555
9199
  fn: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.data.text), 1)]),
8556
9200
  key: "1"
8557
- } : void 0]), 1032, ["disabled", "title"]))], 64)) : __props.data.type === "dropdown" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDropdown), {
9201
+ } : void 0]), 1040, ["disabled", "title"]))], 64)) : __props.data.type === "dropdown" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDropdown), {
8558
9202
  key: 3,
8559
9203
  trigger: "click",
8560
9204
  disabled: disabled.value,
@@ -8575,7 +9219,7 @@
8575
9219
  }), 128))]),
8576
9220
  _: 1
8577
9221
  })) : (0, vue.createCommentVNode)("v-if", true)]),
8578
- 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" }, {
9222
+ 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" }, {
8579
9223
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_element_plus_icons_vue.ArrowDown))]),
8580
9224
  _: 1
8581
9225
  })])]),
@@ -8594,7 +9238,7 @@
8594
9238
  id: "m-editor-page-bar-add-icon",
8595
9239
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon"
8596
9240
  };
8597
- var _hoisted_2$27 = {
9241
+ var _hoisted_2$29 = {
8598
9242
  key: 1,
8599
9243
  style: { "width": "21px" }
8600
9244
  };
@@ -8631,7 +9275,7 @@
8631
9275
  }
8632
9276
  } }, null, 8, ["data"])]),
8633
9277
  _: 1
8634
- })])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$27));
9278
+ })])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$29));
8635
9279
  };
8636
9280
  }
8637
9281
  });
@@ -8644,7 +9288,7 @@
8644
9288
  class: "m-editor-page-bar",
8645
9289
  ref: "pageBar"
8646
9290
  };
8647
- var _hoisted_2$26 = {
9291
+ var _hoisted_2$28 = {
8648
9292
  key: 0,
8649
9293
  class: "m-editor-page-bar-items",
8650
9294
  ref: "itemsContainer"
@@ -8736,7 +9380,7 @@
8736
9380
  return (_ctx, _cache) => {
8737
9381
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$68, [
8738
9382
  (0, vue.renderSlot)(_ctx.$slots, "prepend"),
8739
- __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),
9383
+ __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),
8740
9384
  canScroll.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
8741
9385
  key: 1,
8742
9386
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon m-editor-page-bar-item-left-icon",
@@ -8761,8 +9405,8 @@
8761
9405
  id: "m-editor-page-bar-list-icon",
8762
9406
  class: "m-editor-page-bar-item m-editor-page-bar-item-icon"
8763
9407
  };
8764
- var _hoisted_2$25 = { class: "page-bar-popover-wrapper" };
8765
- var _hoisted_3$10 = { class: "page-bar-popover-inner" };
9408
+ var _hoisted_2$27 = { class: "page-bar-popover-wrapper" };
9409
+ var _hoisted_3$13 = { class: "page-bar-popover-inner" };
8766
9410
  var PageList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8767
9411
  name: "MEditorPageList",
8768
9412
  __name: "PageList",
@@ -8786,7 +9430,7 @@
8786
9430
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_element_plus_icons_vue.Files))]),
8787
9431
  _: 1
8788
9432
  })]),
8789
- 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) => {
9433
+ 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) => {
8790
9434
  return (0, vue.openBlock)(), (0, vue.createBlock)(ToolButton_default, {
8791
9435
  data: {
8792
9436
  type: "button",
@@ -8865,9 +9509,9 @@
8865
9509
  //#endregion
8866
9510
  //#region packages/editor/src/layouts/page-bar/PageBar.vue?vue&type=script&setup=true&lang.ts
8867
9511
  var _hoisted_1$65 = { class: "m-editor-page-bar-tabs" };
8868
- var _hoisted_2$24 = ["data-page-id", "onClick"];
8869
- var _hoisted_3$9 = { class: "m-editor-page-bar-title" };
8870
- var _hoisted_4$8 = ["title"];
9512
+ var _hoisted_2$26 = ["data-page-id", "onClick"];
9513
+ var _hoisted_3$12 = { class: "m-editor-page-bar-title" };
9514
+ var _hoisted_4$9 = ["title"];
8871
9515
  var PageBar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8872
9516
  name: "MEditorPageBar",
8873
9517
  __name: "PageBar",
@@ -8909,7 +9553,8 @@
8909
9553
  top: 0
8910
9554
  });
8911
9555
  };
8912
- const remove = (node) => {
9556
+ const remove = async (node) => {
9557
+ await _tmagic_design.tMagicMessageBox.confirm("确定删除该页面吗?");
8913
9558
  editorService.remove(node);
8914
9559
  };
8915
9560
  const pageBarScrollContainerRef = (0, vue.useTemplateRef)("pageBarScrollContainer");
@@ -8956,7 +9601,7 @@
8956
9601
  key: item.id,
8957
9602
  "data-page-id": item.id,
8958
9603
  onClick: ($event) => switchPage(item.id)
8959
- }, [(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), {
9604
+ }, [(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), {
8960
9605
  "popper-class": "page-bar-popover",
8961
9606
  placement: "top",
8962
9607
  trigger: "hover",
@@ -8976,10 +9621,11 @@
8976
9621
  type: "button",
8977
9622
  text: "删除",
8978
9623
  icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
9624
+ buttonProps: { type: "danger" },
8979
9625
  handler: () => remove(item)
8980
9626
  } }, null, 8, ["data"])])])]),
8981
9627
  _: 2
8982
- }, 1024)], 10, _hoisted_2$24);
9628
+ }, 1024)], 10, _hoisted_2$26);
8983
9629
  }), 128))]),
8984
9630
  _: 3
8985
9631
  }, 8, ["page-bar-sort-options", "length"])]);
@@ -8992,7 +9638,7 @@
8992
9638
  //#endregion
8993
9639
  //#region packages/editor/src/layouts/AddPageBox.vue?vue&type=script&setup=true&lang.ts
8994
9640
  var _hoisted_1$64 = { class: "m-editor-empty-panel" };
8995
- var _hoisted_2$23 = { class: "m-editor-empty-content" };
9641
+ var _hoisted_2$25 = { class: "m-editor-empty-content" };
8996
9642
  var AddPageBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
8997
9643
  name: "MEditorAddPageBox",
8998
9644
  __name: "AddPageBox",
@@ -9009,7 +9655,7 @@
9009
9655
  });
9010
9656
  };
9011
9657
  return (_ctx, _cache) => {
9012
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$64, [(0, vue.createElementVNode)("div", _hoisted_2$23, [(0, vue.createElementVNode)("div", {
9658
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$64, [(0, vue.createElementVNode)("div", _hoisted_2$25, [(0, vue.createElementVNode)("div", {
9013
9659
  class: "m-editor-empty-button",
9014
9660
  onClick: _cache[0] || (_cache[0] = ($event) => clickHandler((0, vue.unref)(_tmagic_core.NodeType).PAGE))
9015
9661
  }, [(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", {
@@ -9026,7 +9672,7 @@
9026
9672
  //#endregion
9027
9673
  //#region packages/editor/src/layouts/CodeEditor.vue?vue&type=script&setup=true&lang.ts
9028
9674
  var _hoisted_1$63 = { class: "magic-code-editor" };
9029
- var _hoisted_2$22 = {
9675
+ var _hoisted_2$24 = {
9030
9676
  ref: "codeEditor",
9031
9677
  class: "magic-code-editor-content"
9032
9678
  };
@@ -9281,7 +9927,7 @@
9281
9927
  }, {
9282
9928
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(_element_plus_icons_vue.FullScreen) }, null, 8, ["icon"])]),
9283
9929
  _: 1
9284
- })) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createElementVNode)("div", _hoisted_2$22, null, 512)], 6)], 8, ["disabled"]))]);
9930
+ })) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createElementVNode)("div", _hoisted_2$24, null, 512)], 6)], 8, ["disabled"]))]);
9285
9931
  };
9286
9932
  }
9287
9933
  });
@@ -9348,7 +9994,7 @@
9348
9994
  const saveCode = (value) => {
9349
9995
  try {
9350
9996
  const parseDSL = getEditorConfig("parseDSL");
9351
- editorService.set("root", parseDSL(value));
9997
+ editorService.set("root", parseDSL(value), { historySource: "root-code" });
9352
9998
  } catch (e) {
9353
9999
  console.error(e);
9354
10000
  }
@@ -9422,6 +10068,8 @@
9422
10068
  var Framework_default = Framework_vue_vue_type_script_setup_true_lang_default;
9423
10069
  //#endregion
9424
10070
  //#region packages/editor/src/layouts/history-list/composables.ts
10071
+ /** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
10072
+ var isHistoryGroupExpanded = (expanded, key) => expanded[key] !== false;
9425
10073
  /**
9426
10074
  * 历史记录面板共享逻辑:
9427
10075
  * - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
@@ -9435,10 +10083,11 @@
9435
10083
  /**
9436
10084
  * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
9437
10085
  * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
10086
+ * 合并组默认展开;仅当值为 `false` 时表示收起。
9438
10087
  */
9439
10088
  const expanded = (0, vue.reactive)({});
9440
10089
  const toggleGroup = (key) => {
9441
- expanded[key] = !expanded[key];
10090
+ expanded[key] = expanded[key] === false;
9442
10091
  };
9443
10092
  const pageGroups = (0, vue.computed)(() => historyService.getPageHistoryGroups());
9444
10093
  const dataSourceGroups = (0, vue.computed)(() => historyService.getDataSourceHistoryGroups());
@@ -9500,6 +10149,7 @@
9500
10149
  "component-panel": "组件面板",
9501
10150
  props: "配置面板",
9502
10151
  code: "源码",
10152
+ "root-code": "DSL源码",
9503
10153
  "stage-contextmenu": "画布菜单",
9504
10154
  "tree-contextmenu": "树菜单",
9505
10155
  toolbar: "工具栏",
@@ -9507,6 +10157,8 @@
9507
10157
  rollback: "回滚",
9508
10158
  api: "接口",
9509
10159
  ai: "AI",
10160
+ initial: "初始值",
10161
+ sync: "同步",
9510
10162
  unknown: "未知"
9511
10163
  };
9512
10164
  /** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
@@ -9515,6 +10167,37 @@
9515
10167
  };
9516
10168
  /** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
9517
10169
  var groupSource = (group) => group.steps[group.steps.length - 1]?.step.source;
10170
+ /**
10171
+ * 把一个历史分组(页面 / bucket)派生为 GroupRow 直接消费的视图模型 {@link HistoryRowGroup}。
10172
+ * 统一了原先 PageTab / Bucket 各自内联的 sub-steps 映射逻辑:描述、可差异、可回滚、时间、途径
10173
+ * 全部在此一次性算好,组件层只负责渲染。
10174
+ */
10175
+ var toRowGroup = (group, key, descriptor) => {
10176
+ const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
10177
+ const timestamp = groupTimestamp(group);
10178
+ return {
10179
+ key,
10180
+ applied: group.applied,
10181
+ isCurrent: Boolean(group.isCurrent),
10182
+ opType: group.opType,
10183
+ desc: describeGroup(group),
10184
+ source: groupSource(group),
10185
+ time: formatHistoryTime(timestamp),
10186
+ timeTitle: formatHistoryFullTime(timestamp),
10187
+ subSteps: group.steps.map((s) => ({
10188
+ index: s.index,
10189
+ applied: s.applied,
10190
+ isCurrent: s.isCurrent,
10191
+ saved: s.step.saved,
10192
+ desc: describeStep(s.step),
10193
+ diffable: isStepDiffable ? isStepDiffable(s.step) : false,
10194
+ revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
10195
+ source: s.step.source,
10196
+ time: formatHistoryTime(s.step.timestamp),
10197
+ timeTitle: formatHistoryFullTime(s.step.timestamp)
10198
+ }))
10199
+ };
10200
+ };
9518
10201
  var nameOf = (node) => node?.name || node?.type || `${node?.id ?? ""}`;
9519
10202
  /**
9520
10203
  * 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
@@ -9533,24 +10216,24 @@
9533
10216
  var describePageStep = (step) => {
9534
10217
  if (step.historyDescription) return step.historyDescription;
9535
10218
  const { opType } = step;
10219
+ const items = step.diff ?? [];
9536
10220
  if (opType === "add") {
9537
- const count = step.nodes?.length ?? 0;
9538
- const node = step.nodes?.[0];
10221
+ const count = items.length;
10222
+ const node = items[0]?.newSchema;
9539
10223
  return `新增 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
9540
10224
  }
9541
10225
  if (opType === "remove") {
9542
- const count = step.removedItems?.length ?? 0;
9543
- const node = step.removedItems?.[0]?.node;
10226
+ const count = items.length;
10227
+ const node = items[0]?.oldSchema;
9544
10228
  return `删除 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
9545
10229
  }
9546
- const updated = step.updatedItems ?? [];
9547
- if (!updated.length) return "修改节点";
9548
- if (updated.length === 1) {
9549
- const { newNode, changeRecords } = updated[0];
10230
+ if (!items.length) return "修改节点";
10231
+ if (items.length === 1) {
10232
+ const { newSchema, changeRecords } = items[0];
9550
10233
  const propPath = changeRecords?.[0]?.propPath;
9551
- return `修改 ${labelWithId(nameOf(newNode), newNode?.id)}${propPath ? ` · ${propPath}` : ""}`;
10234
+ return `修改 ${labelWithId(nameOf(newSchema), newSchema?.id)}${propPath ? ` · ${propPath}` : ""}`;
9552
10235
  }
9553
- return `修改 ${updated.length} 个节点`;
10236
+ return `修改 ${items.length} 个节点`;
9554
10237
  };
9555
10238
  /**
9556
10239
  * 合并组的展示文案:
@@ -9564,7 +10247,7 @@
9564
10247
  if (group.steps.length === 1) return describePageStep(group.steps[0].step);
9565
10248
  const paths = /* @__PURE__ */ new Set();
9566
10249
  group.steps.forEach((s) => {
9567
- s.step.updatedItems?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10250
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9568
10251
  });
9569
10252
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9570
10253
  const target = labelWithId(group.targetName ?? (group.targetId !== void 0 ? `${group.targetId}` : "节点"), group.targetId);
@@ -9572,10 +10255,11 @@
9572
10255
  };
9573
10256
  var describeDataSourceStep = (step) => {
9574
10257
  if (step.historyDescription) return step.historyDescription;
9575
- if (step.oldSchema === null && step.newSchema) return `创建 ${labelWithId(step.newSchema.title, step.newSchema.id ?? step.id)}`;
9576
- if (step.newSchema === null && step.oldSchema) return `删除 ${labelWithId(step.oldSchema.title, step.oldSchema.id ?? step.id)}`;
9577
- const propPath = step.changeRecords?.[0]?.propPath;
9578
- const title = labelWithId(step.newSchema?.title || step.oldSchema?.title, step.id);
10258
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
10259
+ if (!oldSchema && newSchema) return `创建 ${labelWithId(newSchema.title, newSchema.id ?? step.id)}`;
10260
+ if (!newSchema && oldSchema) return `删除 ${labelWithId(oldSchema.title, oldSchema.id ?? step.id)}`;
10261
+ const propPath = changeRecords?.[0]?.propPath;
10262
+ const title = labelWithId(newSchema?.title || oldSchema?.title, step.id);
9579
10263
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
9580
10264
  };
9581
10265
  var describeDataSourceGroup = (group) => {
@@ -9584,18 +10268,19 @@
9584
10268
  if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
9585
10269
  const paths = /* @__PURE__ */ new Set();
9586
10270
  group.steps.forEach((s) => {
9587
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10271
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9588
10272
  });
9589
10273
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9590
- const target = labelWithId(group.steps[group.steps.length - 1].step.newSchema?.title || group.steps[0].step.oldSchema?.title, group.id);
10274
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.title || group.steps[0].step.diff?.[0]?.oldSchema?.title, group.id);
9591
10275
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
9592
10276
  };
9593
10277
  var describeCodeBlockStep = (step) => {
9594
10278
  if (step.historyDescription) return step.historyDescription;
9595
- if (step.oldContent === null && step.newContent) return `创建 ${labelWithId(step.newContent.name, step.newContent.id ?? step.id)}`;
9596
- if (step.newContent === null && step.oldContent) return `删除 ${labelWithId(step.oldContent.name, step.oldContent.id ?? step.id)}`;
9597
- const propPath = step.changeRecords?.[0]?.propPath;
9598
- const title = labelWithId(step.newContent?.name || step.oldContent?.name, step.id);
10279
+ const { oldSchema: oldContent, newSchema: newContent, changeRecords } = step.diff?.[0] ?? {};
10280
+ if (!oldContent && newContent) return `创建 ${labelWithId(newContent.name, newContent.id ?? step.id)}`;
10281
+ if (!newContent && oldContent) return `删除 ${labelWithId(oldContent.name, oldContent.id ?? step.id)}`;
10282
+ const propPath = changeRecords?.[0]?.propPath;
10283
+ const title = labelWithId(newContent?.name || oldContent?.name, step.id);
9599
10284
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
9600
10285
  };
9601
10286
  var describeCodeBlockGroup = (group) => {
@@ -9604,10 +10289,10 @@
9604
10289
  if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
9605
10290
  const paths = /* @__PURE__ */ new Set();
9606
10291
  group.steps.forEach((s) => {
9607
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
10292
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
9608
10293
  });
9609
10294
  const pathList = Array.from(paths).slice(0, 3).join(", ");
9610
- const target = labelWithId(group.steps[group.steps.length - 1].step.newContent?.name || group.steps[0].step.oldContent?.name, group.id);
10295
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.name || group.steps[0].step.diff?.[0]?.oldSchema?.name, group.id);
9611
10296
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
9612
10297
  };
9613
10298
  /**
@@ -9618,136 +10303,176 @@
9618
10303
  */
9619
10304
  var isPageStepRevertable = (step) => {
9620
10305
  if (step.opType !== "update") return true;
9621
- const items = step.updatedItems ?? [];
10306
+ const items = step.diff ?? [];
9622
10307
  if (!items.length) return false;
9623
10308
  return items.every((item) => Boolean(item.changeRecords?.length));
9624
10309
  };
9625
10310
  /**
9626
10311
  * 数据源 step 是否支持「回滚」:
9627
- * - 新增(oldSchema=null)/ 删除(newSchema=null):不依赖 changeRecords,始终可回滚;
10312
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
9628
10313
  * - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
9629
10314
  */
9630
10315
  var isDataSourceStepRevertable = (step) => {
9631
- if (step.oldSchema === null || step.newSchema === null) return true;
9632
- return Boolean(step.changeRecords?.length);
10316
+ const item = step.diff?.[0];
10317
+ if (!item?.oldSchema || !item?.newSchema) return true;
10318
+ return Boolean(item.changeRecords?.length);
9633
10319
  };
9634
10320
  /**
9635
10321
  * 代码块 step 是否支持「回滚」:
9636
- * - 新增(oldContent=null)/ 删除(newContent=null):不依赖 changeRecords,始终可回滚;
10322
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
9637
10323
  * - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
9638
10324
  */
9639
10325
  var isCodeBlockStepRevertable = (step) => {
9640
- if (step.oldContent === null || step.newContent === null) return true;
9641
- return Boolean(step.changeRecords?.length);
10326
+ const item = step.diff?.[0];
10327
+ if (!item?.oldSchema || !item?.newSchema) return true;
10328
+ return Boolean(item.changeRecords?.length);
9642
10329
  };
9643
10330
  //#endregion
9644
10331
  //#region packages/editor/src/layouts/history-list/GroupRow.vue?vue&type=script&setup=true&lang.ts
9645
10332
  var _hoisted_1$61 = ["title"];
9646
- var _hoisted_2$21 = ["title"];
9647
- var _hoisted_3$8 = { class: "m-editor-history-list-item-desc" };
9648
- var _hoisted_4$7 = ["title"];
9649
- var _hoisted_5$3 = ["title"];
9650
- var _hoisted_6$3 = {
9651
- key: 2,
10333
+ var _hoisted_2$23 = ["title"];
10334
+ var _hoisted_3$11 = { class: "m-editor-history-list-item-desc" };
10335
+ var _hoisted_4$8 = {
10336
+ key: 0,
10337
+ class: "m-editor-history-list-item-saved",
10338
+ title: "该记录为最近一次保存的状态"
10339
+ };
10340
+ var _hoisted_5$4 = {
10341
+ key: 1,
10342
+ class: "m-editor-history-list-item-actions"
10343
+ };
10344
+ var _hoisted_6$3 = ["title"];
10345
+ var _hoisted_7$1 = ["title"];
10346
+ var _hoisted_8 = {
10347
+ key: 4,
9652
10348
  class: "m-editor-history-list-item-merge"
9653
10349
  };
9654
- var _hoisted_7$1 = {
10350
+ var _hoisted_9 = {
9655
10351
  key: 0,
9656
10352
  class: "m-editor-history-list-substeps"
9657
10353
  };
9658
- var _hoisted_8 = ["title"];
9659
- var _hoisted_9 = { class: "m-editor-history-list-item-index" };
9660
- var _hoisted_10 = { class: "m-editor-history-list-substep-desc" };
9661
- var _hoisted_11 = ["title"];
9662
- var _hoisted_12 = ["title"];
9663
- var _hoisted_13 = ["onClick"];
9664
- var _hoisted_14 = ["onClick"];
10354
+ var _hoisted_10 = ["title", "onClick"];
10355
+ var _hoisted_11 = { class: "m-editor-history-list-item-index" };
10356
+ var _hoisted_12 = { class: "m-editor-history-list-substep-desc" };
10357
+ var _hoisted_13 = {
10358
+ key: 0,
10359
+ class: "m-editor-history-list-item-saved",
10360
+ title: "该记录为最近一次保存的状态"
10361
+ };
10362
+ var _hoisted_14 = {
10363
+ key: 1,
10364
+ class: "m-editor-history-list-item-actions"
10365
+ };
9665
10366
  var _hoisted_15 = ["onClick"];
10367
+ var _hoisted_16 = ["onClick"];
10368
+ var _hoisted_17 = ["onClick"];
10369
+ var _hoisted_18 = ["title"];
10370
+ var _hoisted_19 = ["title"];
9666
10371
  var GroupRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9667
10372
  name: "MEditorHistoryListGroupRow",
9668
10373
  __name: "GroupRow",
9669
10374
  props: {
9670
- groupKey: {},
9671
- applied: { type: Boolean },
9672
- merged: { type: Boolean },
9673
- opType: {},
9674
- desc: {},
9675
- source: {},
9676
- time: {},
9677
- timeTitle: {},
9678
- stepCount: {},
9679
- subSteps: {},
10375
+ group: {},
9680
10376
  expanded: { type: Boolean },
9681
- isCurrent: {
9682
- type: Boolean,
9683
- default: false
9684
- },
9685
10377
  gotoEnabled: {
9686
10378
  type: Boolean,
9687
10379
  default: true
10380
+ },
10381
+ selectEnabled: {
10382
+ type: Boolean,
10383
+ default: false
9688
10384
  }
9689
10385
  },
9690
10386
  emits: [
9691
10387
  "toggle",
9692
10388
  "goto",
9693
10389
  "diff-step",
9694
- "revert-step"
10390
+ "revert-step",
10391
+ "select"
9695
10392
  ],
9696
10393
  setup(__props, { emit: __emit }) {
9697
10394
  const props = __props;
9698
10395
  const emit = __emit;
10396
+ /** 子步数大于 1 即为合并组:决定是否展示合并标记与可展开的子步列表。 */
10397
+ const merged = (0, vue.computed)(() => props.group.subSteps.length > 1);
10398
+ /** 组内 step 总数,仅在合并组时显示为 "合并 N 步"。 */
10399
+ const stepCount = (0, vue.computed)(() => props.group.subSteps.length);
9699
10400
  /**
9700
- * 仅合并组头部可点击(切换展开 / 收起);
9701
- * 单步组的跳转改由头部的「回退」按钮触发,整行不再可点击。
10401
+ * 头部可点击的场景:
10402
+ * - 合并组:点击切换展开 / 收起;
10403
+ * - 开启 `selectEnabled`(页面 tab):点击选中对应节点。
10404
+ * 单步组的跳转仍由头部的「回到」按钮触发。
9702
10405
  */
9703
- const isHeadClickable = (0, vue.computed)(() => props.merged);
10406
+ const isHeadClickable = (0, vue.computed)(() => merged.value || props.selectEnabled);
9704
10407
  const headTitle = (0, vue.computed)(() => {
9705
- if (props.merged) return props.expanded ? "点击收起子步" : "点击展开子步";
9706
- if (props.isCurrent) return "当前所在记录";
10408
+ if (merged.value) {
10409
+ const expandHint = props.expanded ? "点击收起子步" : "点击展开子步";
10410
+ return props.selectEnabled ? `${expandHint}(并选中该节点)` : expandHint;
10411
+ }
10412
+ if (props.selectEnabled) return "点击选中该节点";
10413
+ if (props.group.isCurrent) return "当前所在记录";
9707
10414
  return "";
9708
10415
  });
9709
10416
  /**
9710
- * 头部点击行为:仅合并组切换展开 / 收起;单步组不再响应整行点击。
10417
+ * 头部点击行为:
10418
+ * - 开启 selectEnabled 时,发出 select(携带组内首步 index,上层据此选中节点);
10419
+ * - 合并组同时切换展开 / 收起。
9711
10420
  */
9712
10421
  const onHeadClick = () => {
9713
- if (props.merged) emit("toggle", props.groupKey);
10422
+ if (props.selectEnabled && props.group.subSteps.length) emit("select", props.group.subSteps[0].index);
10423
+ if (merged.value) emit("toggle", props.group.key);
9714
10424
  };
9715
10425
  const onGotoClick = (index) => {
9716
10426
  if (!props.gotoEnabled) return;
9717
10427
  emit("goto", index);
9718
10428
  };
10429
+ /** 点击子步行:开启 selectEnabled 时选中该子步对应的节点。 */
10430
+ const onSubStepClick = (index) => {
10431
+ if (!props.selectEnabled) return;
10432
+ emit("select", index);
10433
+ };
9719
10434
  const subStepTitle = (s) => {
10435
+ if (props.selectEnabled) return "点击选中该节点";
9720
10436
  if (s.isCurrent) return "当前所在记录";
9721
10437
  return "";
9722
10438
  };
10439
+ /**
10440
+ * 头部是否展示「已保存」标记:
10441
+ * - 单步组:取该唯一子步的 saved;
10442
+ * - 合并组:组内任一子步为已保存即在头部提示(具体落在哪一步可展开查看)。
10443
+ */
10444
+ const headSaved = (0, vue.computed)(() => merged.value ? props.group.subSteps.some((s) => s.saved) : Boolean(props.group.subSteps[0]?.saved));
9723
10445
  /** 单步组头部是否展示"查看差异"入口:要求该唯一子步本身可对比。 */
9724
- const headDiffable = (0, vue.computed)(() => !props.merged && Boolean(props.subSteps[0]?.diffable));
10446
+ const headDiffable = (0, vue.computed)(() => !merged.value && Boolean(props.group.subSteps[0]?.diffable));
9725
10447
  /** 单步组头部是否展示"回滚"入口:要求该唯一子步本身可回滚(已应用)。 */
9726
- const headRevertable = (0, vue.computed)(() => !props.merged && Boolean(props.subSteps[0]?.revertable));
10448
+ const headRevertable = (0, vue.computed)(() => !merged.value && Boolean(props.group.subSteps[0]?.revertable));
10449
+ /** 单步组头部是否展示"回到"入口:可跳转、非当前、且存在唯一子步。 */
10450
+ const canHeadGoto = (0, vue.computed)(() => !merged.value && props.gotoEnabled && !props.group.isCurrent && props.group.subSteps.length > 0);
9727
10451
  /**
9728
10452
  * 合并组展开后的子步渲染顺序:与外层分组列表保持一致——倒序展示(最新的子步在最上方)。
9729
10453
  * 外层 page tab / bucket 都已对 groups 做了 reverse,子步沿用同样的视觉规则更直观。
9730
10454
  * 注意:仅用于渲染,原 `subSteps` 保持时间正序,`headIndexLabel` 等基于首尾索引的展示语义不变。
9731
10455
  */
9732
- const subStepsDisplay = (0, vue.computed)(() => props.subSteps.slice().reverse());
10456
+ const subStepsDisplay = (0, vue.computed)(() => props.group.subSteps.slice().reverse());
9733
10457
  /**
9734
10458
  * 头部索引展示:
9735
- * - 单步组(merged=false):显示该唯一 step 的编号,如 `#5`;
10459
+ * - 单步组(非合并):显示该唯一 step 的编号,如 `#5`;
9736
10460
  * - 合并组:显示组内 step 的编号范围,如 `#3-#7`(首尾相同则退化为 `#5`)。
9737
10461
  *
9738
10462
  * 这里展示的是 step.index + 1(与子步列表 `#{{ s.index + 1 }}` 保持一致),从 1 起编号更符合直觉。
9739
10463
  */
9740
10464
  const headIndexLabel = (0, vue.computed)(() => {
9741
- const list = props.subSteps;
10465
+ const list = props.group.subSteps;
9742
10466
  if (!list.length) return "";
9743
10467
  const first = list[0].index + 1;
9744
10468
  const last = list[list.length - 1].index + 1;
9745
- if (!props.merged || first === last) return `#${first}`;
10469
+ if (!merged.value || first === last) return `#${first}`;
9746
10470
  return `#${first}-#${last}`;
9747
10471
  });
9748
10472
  const headIndexTitle = (0, vue.computed)(() => {
9749
- if (!props.merged) return `历史步骤编号 #${props.subSteps[0]?.index + 1}`;
9750
- return `合并了第 ${props.subSteps[0]?.index + 1} 至第 ${props.subSteps[props.subSteps.length - 1]?.index + 1} 共 ${props.subSteps.length} 条历史步骤`;
10473
+ const list = props.group.subSteps;
10474
+ if (!merged.value) return `历史步骤编号 #${list[0]?.index + 1}`;
10475
+ return `合并了第 ${list[0]?.index + 1} 至第 ${list[list.length - 1]?.index + 1} 共 ${list.length} 条历史步骤`;
9751
10476
  });
9752
10477
  const onDiffClick = (index) => {
9753
10478
  emit("diff-step", index);
@@ -9757,9 +10482,9 @@
9757
10482
  };
9758
10483
  return (_ctx, _cache) => {
9759
10484
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("li", { class: (0, vue.normalizeClass)(["m-editor-history-list-item m-editor-history-list-group", {
9760
- "is-undone": !__props.applied,
9761
- "is-merged": __props.merged,
9762
- "is-current": __props.isCurrent
10485
+ "is-undone": !__props.group.applied,
10486
+ "is-merged": merged.value,
10487
+ "is-current": __props.group.isCurrent
9763
10488
  }]) }, [(0, vue.createElementVNode)("div", {
9764
10489
  class: (0, vue.normalizeClass)(["m-editor-history-list-group-head", { "is-clickable": isHeadClickable.value }]),
9765
10490
  title: headTitle.value,
@@ -9768,82 +10493,90 @@
9768
10493
  (0, vue.createElementVNode)("span", {
9769
10494
  class: "m-editor-history-list-item-index",
9770
10495
  title: headIndexTitle.value
9771
- }, (0, vue.toDisplayString)(headIndexLabel.value), 9, _hoisted_2$21),
9772
- (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),
9773
- (0, vue.createElementVNode)("span", _hoisted_3$8, (0, vue.toDisplayString)(__props.desc), 1),
9774
- !__props.merged && (0, vue.unref)(sourceLabel)(__props.source) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9775
- key: 0,
10496
+ }, (0, vue.toDisplayString)(headIndexLabel.value), 9, _hoisted_2$23),
10497
+ (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),
10498
+ (0, vue.createElementVNode)("span", _hoisted_3$11, (0, vue.toDisplayString)(__props.group.desc), 1),
10499
+ headSaved.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$8, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10500
+ !merged.value && (headRevertable.value || headDiffable.value || canHeadGoto.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$4, [
10501
+ headRevertable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10502
+ key: 0,
10503
+ class: "m-editor-history-list-item-revert",
10504
+ title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
10505
+ onClick: _cache[0] || (_cache[0] = (0, vue.withModifiers)(($event) => onRevertClick(__props.group.subSteps[0].index), ["stop"]))
10506
+ }, "回滚")) : (0, vue.createCommentVNode)("v-if", true),
10507
+ canHeadGoto.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10508
+ key: 1,
10509
+ class: "m-editor-history-list-item-goto",
10510
+ title: "回到该记录",
10511
+ onClick: _cache[1] || (_cache[1] = (0, vue.withModifiers)(($event) => onGotoClick(__props.group.subSteps[0].index), ["stop"]))
10512
+ }, "回到")) : (0, vue.createCommentVNode)("v-if", true),
10513
+ headDiffable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10514
+ key: 2,
10515
+ class: "m-editor-history-list-item-diff",
10516
+ title: "查看修改差异",
10517
+ onClick: _cache[2] || (_cache[2] = (0, vue.withModifiers)(($event) => onDiffClick(__props.group.subSteps[0].index), ["stop"]))
10518
+ }, "查看差异")) : (0, vue.createCommentVNode)("v-if", true)
10519
+ ])) : (0, vue.createCommentVNode)("v-if", true),
10520
+ !merged.value && (0, vue.unref)(sourceLabel)(__props.group.source) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10521
+ key: 2,
9776
10522
  class: "m-editor-history-list-item-source",
9777
- title: `操作途径:${(0, vue.unref)(sourceLabel)(__props.source)}`
9778
- }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(__props.source)), 9, _hoisted_4$7)) : (0, vue.createCommentVNode)("v-if", true),
9779
- !__props.merged && __props.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9780
- key: 1,
9781
- class: "m-editor-history-list-item-time",
9782
- title: __props.timeTitle || __props.time
9783
- }, (0, vue.toDisplayString)(__props.time), 9, _hoisted_5$3)) : (0, vue.createCommentVNode)("v-if", true),
9784
- __props.merged ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_6$3, "合并 " + (0, vue.toDisplayString)(__props.stepCount) + " 步", 1)) : (0, vue.createCommentVNode)("v-if", true),
9785
- !__props.merged && headRevertable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10523
+ title: `操作途径:${(0, vue.unref)(sourceLabel)(__props.group.source)}`
10524
+ }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(__props.group.source)), 9, _hoisted_6$3)) : (0, vue.createCommentVNode)("v-if", true),
10525
+ !merged.value && __props.group.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9786
10526
  key: 3,
9787
- class: "m-editor-history-list-item-revert",
9788
- title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
9789
- onClick: _cache[0] || (_cache[0] = (0, vue.withModifiers)(($event) => onRevertClick(__props.subSteps[0].index), ["stop"]))
9790
- }, "回滚")) : (0, vue.createCommentVNode)("v-if", true),
9791
- !__props.merged && __props.gotoEnabled && !__props.isCurrent && __props.subSteps.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9792
- key: 4,
9793
- class: "m-editor-history-list-item-goto",
9794
- title: "回到该记录",
9795
- onClick: _cache[1] || (_cache[1] = (0, vue.withModifiers)(($event) => onGotoClick(__props.subSteps[0].index), ["stop"]))
9796
- }, "回到")) : (0, vue.createCommentVNode)("v-if", true),
9797
- !__props.merged && headDiffable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10527
+ class: "m-editor-history-list-item-time",
10528
+ title: __props.group.timeTitle || __props.group.time
10529
+ }, (0, vue.toDisplayString)(__props.group.time), 9, _hoisted_7$1)) : (0, vue.createCommentVNode)("v-if", true),
10530
+ merged.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8, "合并 " + (0, vue.toDisplayString)(stepCount.value) + " 步", 1)) : (0, vue.createCommentVNode)("v-if", true),
10531
+ merged.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9798
10532
  key: 5,
9799
- class: "m-editor-history-list-item-diff",
9800
- title: "查看修改差异",
9801
- onClick: _cache[2] || (_cache[2] = (0, vue.withModifiers)(($event) => onDiffClick(__props.subSteps[0].index), ["stop"]))
9802
- }, "查看差异")) : (0, vue.createCommentVNode)("v-if", true),
9803
- __props.merged ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9804
- key: 6,
9805
10533
  class: (0, vue.normalizeClass)(["m-editor-history-list-group-toggle", { "is-expanded": __props.expanded }])
9806
10534
  }, "▾", 2)) : (0, vue.createCommentVNode)("v-if", true)
9807
- ], 10, _hoisted_1$61), __props.merged && __props.expanded ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("ul", _hoisted_7$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(subStepsDisplay.value, (s) => {
10535
+ ], 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) => {
9808
10536
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("li", {
9809
10537
  key: s.index,
9810
10538
  class: (0, vue.normalizeClass)({
9811
10539
  "is-undone": !s.applied,
9812
- "is-current": s.isCurrent
10540
+ "is-current": s.isCurrent,
10541
+ "is-clickable": __props.selectEnabled
9813
10542
  }),
9814
- title: subStepTitle(s)
10543
+ title: subStepTitle(s),
10544
+ onClick: ($event) => onSubStepClick(s.index)
9815
10545
  }, [
9816
- (0, vue.createElementVNode)("span", _hoisted_9, "#" + (0, vue.toDisplayString)(s.index + 1), 1),
9817
- (0, vue.createElementVNode)("span", _hoisted_10, (0, vue.toDisplayString)(s.desc), 1),
10546
+ (0, vue.createElementVNode)("span", _hoisted_11, "#" + (0, vue.toDisplayString)(s.index + 1), 1),
10547
+ (0, vue.createElementVNode)("span", _hoisted_12, (0, vue.toDisplayString)(s.desc), 1),
10548
+ s.saved ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_13, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10549
+ s.revertable || s.diffable || __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_14, [
10550
+ s.revertable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10551
+ key: 0,
10552
+ class: "m-editor-history-list-item-revert",
10553
+ title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
10554
+ onClick: (0, vue.withModifiers)(($event) => onRevertClick(s.index), ["stop"])
10555
+ }, "回滚", 8, _hoisted_15)) : (0, vue.createCommentVNode)("v-if", true),
10556
+ __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10557
+ key: 1,
10558
+ class: "m-editor-history-list-item-goto",
10559
+ title: "回到该记录",
10560
+ onClick: (0, vue.withModifiers)(($event) => onGotoClick(s.index), ["stop"])
10561
+ }, "回到", 8, _hoisted_16)) : (0, vue.createCommentVNode)("v-if", true),
10562
+ s.diffable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10563
+ key: 2,
10564
+ class: "m-editor-history-list-item-diff",
10565
+ title: "查看修改差异",
10566
+ onClick: (0, vue.withModifiers)(($event) => onDiffClick(s.index), ["stop"])
10567
+ }, "查看差异", 8, _hoisted_17)) : (0, vue.createCommentVNode)("v-if", true)
10568
+ ])) : (0, vue.createCommentVNode)("v-if", true),
9818
10569
  (0, vue.unref)(sourceLabel)(s.source) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9819
- key: 0,
10570
+ key: 2,
9820
10571
  class: "m-editor-history-list-item-source",
9821
10572
  title: `操作途径:${(0, vue.unref)(sourceLabel)(s.source)}`
9822
- }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(s.source)), 9, _hoisted_11)) : (0, vue.createCommentVNode)("v-if", true),
10573
+ }, (0, vue.toDisplayString)((0, vue.unref)(sourceLabel)(s.source)), 9, _hoisted_18)) : (0, vue.createCommentVNode)("v-if", true),
9823
10574
  s.time ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9824
- key: 1,
10575
+ key: 3,
9825
10576
  class: "m-editor-history-list-item-time",
9826
10577
  title: s.timeTitle || s.time
9827
- }, (0, vue.toDisplayString)(s.time), 9, _hoisted_12)) : (0, vue.createCommentVNode)("v-if", true),
9828
- s.revertable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9829
- key: 2,
9830
- class: "m-editor-history-list-item-revert",
9831
- title: "将该步骤的修改作为一次新操作反向应用(不影响后续历史)",
9832
- onClick: (0, vue.withModifiers)(($event) => onRevertClick(s.index), ["stop"])
9833
- }, "回滚", 8, _hoisted_13)) : (0, vue.createCommentVNode)("v-if", true),
9834
- __props.gotoEnabled && !s.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9835
- key: 3,
9836
- class: "m-editor-history-list-item-goto",
9837
- title: "回到该记录",
9838
- onClick: (0, vue.withModifiers)(($event) => onGotoClick(s.index), ["stop"])
9839
- }, "回到", 8, _hoisted_14)) : (0, vue.createCommentVNode)("v-if", true),
9840
- s.diffable ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9841
- key: 4,
9842
- class: "m-editor-history-list-item-diff",
9843
- title: "查看修改差异",
9844
- onClick: (0, vue.withModifiers)(($event) => onDiffClick(s.index), ["stop"])
9845
- }, "查看差异", 8, _hoisted_15)) : (0, vue.createCommentVNode)("v-if", true)
9846
- ], 10, _hoisted_8);
10578
+ }, (0, vue.toDisplayString)(s.time), 9, _hoisted_19)) : (0, vue.createCommentVNode)("v-if", true)
10579
+ ], 10, _hoisted_10);
9847
10580
  }), 128))])) : (0, vue.createCommentVNode)("v-if", true)], 2);
9848
10581
  };
9849
10582
  }
@@ -9854,6 +10587,17 @@
9854
10587
  //#endregion
9855
10588
  //#region packages/editor/src/layouts/history-list/InitialRow.vue?vue&type=script&setup=true&lang.ts
9856
10589
  var _hoisted_1$60 = ["title"];
10590
+ var _hoisted_2$22 = { class: "m-editor-history-list-item-desc" };
10591
+ var _hoisted_3$10 = {
10592
+ key: 0,
10593
+ class: "m-editor-history-list-item-saved",
10594
+ title: "该记录为最近一次保存的状态"
10595
+ };
10596
+ var _hoisted_4$7 = {
10597
+ key: 1,
10598
+ class: "m-editor-history-list-item-actions"
10599
+ };
10600
+ var _hoisted_5$3 = ["title"];
9857
10601
  var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9858
10602
  name: "MEditorHistoryListInitialRow",
9859
10603
  __name: "InitialRow",
@@ -9862,7 +10606,8 @@
9862
10606
  gotoEnabled: {
9863
10607
  type: Boolean,
9864
10608
  default: true
9865
- }
10609
+ },
10610
+ marker: { default: void 0 }
9866
10611
  },
9867
10612
  emits: ["goto-initial"],
9868
10613
  setup(__props, { emit: __emit }) {
@@ -9870,10 +10615,21 @@
9870
10615
  * 「初始状态」记录行:渲染于历史列表底部,作为整个栈的"零点"。
9871
10616
  * - 点击该行会把对应栈撤销到 cursor === 0(即没有任何已应用步骤),等同于回到所有修改之前。
9872
10617
  * - 当对应栈本身已处于 cursor === 0 时(isCurrent=true),用户已在初始状态,点击不再触发动作。
10618
+ * - 当上层传入 `marker`(设置 root 时为该页生成的「未修改的初始状态」标记)时,
10619
+ * 用标记的文案与时间渲染本行;标记不进入撤销/重做栈,仅作为该页基线展示。
9873
10620
  *
9874
10621
  * 该行不是真实 step,仅作为 UI 入口;上层负责把"点击"翻译为 `service.goto*(0)`。
9875
10622
  */
9876
10623
  const props = __props;
10624
+ const desc = (0, vue.computed)(() => props.marker?.historyDescription || "未修改的初始状态");
10625
+ /** 基线(初始状态)是否为最近一次保存点:仅页面栈的 `initial` 基线 step 会被 markSaved 标记。 */
10626
+ const saved = (0, vue.computed)(() => Boolean(props.marker?.saved));
10627
+ const time = (0, vue.computed)(() => formatHistoryTime(props.marker?.timestamp));
10628
+ const timeTitle = (0, vue.computed)(() => formatHistoryFullTime(props.marker?.timestamp));
10629
+ const rowTitle = (0, vue.computed)(() => {
10630
+ const base = props.marker?.historyDescription || "未修改的初始状态";
10631
+ return props.isCurrent ? `当前已回到${base}` : `点击回到${base}`;
10632
+ });
9877
10633
  const emit = __emit;
9878
10634
  const onClick = () => {
9879
10635
  if (props.isCurrent) return;
@@ -9885,20 +10641,25 @@
9885
10641
  "is-current": __props.isCurrent,
9886
10642
  "is-clickable": !__props.isCurrent
9887
10643
  }]),
9888
- title: __props.isCurrent ? "当前已回到未修改的初始状态" : "点击回到未修改的初始状态"
10644
+ title: rowTitle.value
9889
10645
  }, [
9890
10646
  _cache[0] || (_cache[0] = (0, vue.createElementVNode)("span", {
9891
10647
  class: "m-editor-history-list-item-index",
9892
10648
  title: "历史步骤编号 #0(未修改的初始状态)"
9893
10649
  }, "#0", -1)),
9894
10650
  _cache[1] || (_cache[1] = (0, vue.createElementVNode)("span", { class: "m-editor-history-list-item-op op-initial" }, "初始", -1)),
9895
- _cache[2] || (_cache[2] = (0, vue.createElementVNode)("span", { class: "m-editor-history-list-item-desc" }, "未修改的初始状态", -1)),
9896
- __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
9897
- key: 0,
10651
+ (0, vue.createElementVNode)("span", _hoisted_2$22, (0, vue.toDisplayString)(desc.value), 1),
10652
+ saved.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$10, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10653
+ __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$7, [(0, vue.createElementVNode)("span", {
9898
10654
  class: "m-editor-history-list-item-goto",
9899
10655
  title: "回到该记录",
9900
10656
  onClick: (0, vue.withModifiers)(onClick, ["stop"])
9901
- }, "回到")) : (0, vue.createCommentVNode)("v-if", true)
10657
+ }, "回到")])) : (0, vue.createCommentVNode)("v-if", true),
10658
+ time.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10659
+ key: 2,
10660
+ class: "m-editor-history-list-item-time",
10661
+ title: timeTitle.value
10662
+ }, (0, vue.toDisplayString)(time.value), 9, _hoisted_5$3)) : (0, vue.createCommentVNode)("v-if", true)
9902
10663
  ], 10, _hoisted_1$60);
9903
10664
  };
9904
10665
  }
@@ -9909,30 +10670,17 @@
9909
10670
  //#endregion
9910
10671
  //#region packages/editor/src/layouts/history-list/Bucket.vue?vue&type=script&setup=true&lang.ts
9911
10672
  var _hoisted_1$59 = { class: "m-editor-history-list-bucket" };
9912
- var _hoisted_2$20 = { class: "m-editor-history-list-bucket-title" };
9913
- var _hoisted_3$7 = { class: "m-editor-history-list-bucket-count" };
10673
+ var _hoisted_2$21 = { class: "m-editor-history-list-bucket-title" };
10674
+ var _hoisted_3$9 = { class: "m-editor-history-list-bucket-count" };
9914
10675
  var _hoisted_4$6 = { class: "m-editor-history-list-ul" };
9915
10676
  var Bucket_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
9916
10677
  name: "MEditorHistoryListBucket",
9917
10678
  __name: "Bucket",
9918
10679
  props: {
9919
- title: {},
10680
+ config: {},
9920
10681
  bucketId: {},
9921
- prefix: {},
9922
- showInitial: {
9923
- type: Boolean,
9924
- default: true
9925
- },
9926
10682
  groups: {},
9927
- describeGroup: {},
9928
- describeStep: {},
9929
- isStepDiffable: {},
9930
- isStepRevertable: {},
9931
- expanded: {},
9932
- gotoEnabled: {
9933
- type: Boolean,
9934
- default: true
9935
- }
10683
+ expanded: {}
9936
10684
  },
9937
10685
  emits: [
9938
10686
  "toggle",
@@ -9943,65 +10691,42 @@
9943
10691
  ],
9944
10692
  setup(__props) {
9945
10693
  const props = __props;
10694
+ /**
10695
+ * 子项 / 折叠状态 key:`${prefix}-${bucketId}-${组内首步 index}`。
10696
+ * 以稳定的 step 索引(而非展示位置)标识分组,历史数据更新后已展开的分组状态仍能正确保持。
10697
+ */
10698
+ const rowKey = (group) => `${props.config.prefix}-${props.bucketId}-${group.steps[0]?.index}`;
10699
+ /** 把原始分组派生为 GroupRow 直接消费的视图模型。 */
10700
+ const toRow = (group) => toRowGroup(group, rowKey(group), props.config);
9946
10701
  /** 该 bucket 是否处于初始状态(栈 cursor=0),等价于全部 group 都未 applied。 */
9947
10702
  const isInitial = (0, vue.computed)(() => props.groups.length > 0 && props.groups.every((g) => !g.applied));
9948
10703
  return (_ctx, _cache) => {
9949
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$59, [(0, vue.createElementVNode)("div", _hoisted_2$20, [
9950
- (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.title), 1),
10704
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$59, [(0, vue.createElementVNode)("div", _hoisted_2$21, [
10705
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.config.title), 1),
9951
10706
  (0, vue.createElementVNode)("code", null, (0, vue.toDisplayString)(String(__props.bucketId)), 1),
9952
- (0, vue.createElementVNode)("span", _hoisted_3$7, (0, vue.toDisplayString)(__props.groups.length) + " 组", 1)
10707
+ (0, vue.createElementVNode)("span", _hoisted_3$9, (0, vue.toDisplayString)(__props.groups.length) + " 组", 1)
9953
10708
  ]), (0, vue.createElementVNode)("ul", _hoisted_4$6, [
9954
10709
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.groups, (group) => {
9955
10710
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupRow_default, {
9956
- key: `${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`,
9957
- "group-key": `${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`,
9958
- applied: group.applied,
9959
- merged: group.steps.length > 1,
9960
- "op-type": group.opType,
9961
- desc: __props.describeGroup(group),
9962
- source: (0, vue.unref)(groupSource)(group),
9963
- time: (0, vue.unref)(formatHistoryTime)((0, vue.unref)(groupTimestamp)(group)),
9964
- "time-title": (0, vue.unref)(formatHistoryFullTime)((0, vue.unref)(groupTimestamp)(group)),
9965
- "step-count": group.steps.length,
9966
- "sub-steps": group.steps.map((s) => ({
9967
- index: s.index,
9968
- applied: s.applied,
9969
- isCurrent: s.isCurrent,
9970
- desc: __props.describeStep(s.step),
9971
- diffable: __props.isStepDiffable ? __props.isStepDiffable(s.step) : false,
9972
- revertable: s.applied && (__props.isStepRevertable ? __props.isStepRevertable(s.step) : true),
9973
- source: s.step.source,
9974
- time: (0, vue.unref)(formatHistoryTime)(s.step.timestamp),
9975
- timeTitle: (0, vue.unref)(formatHistoryFullTime)(s.step.timestamp)
9976
- })),
9977
- "is-current": group.isCurrent,
9978
- expanded: !!__props.expanded[`${__props.prefix}-${__props.bucketId}-${group.steps[0]?.index}`],
9979
- "goto-enabled": __props.gotoEnabled,
10711
+ key: rowKey(group),
10712
+ group: toRow(group),
10713
+ expanded: (0, vue.unref)(isHistoryGroupExpanded)(__props.expanded, rowKey(group)),
10714
+ "goto-enabled": __props.config.gotoEnabled,
9980
10715
  onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
9981
10716
  onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", __props.bucketId, index)),
9982
10717
  onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", __props.bucketId, index)),
9983
10718
  onRevertStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("revert-step", __props.bucketId, index))
9984
10719
  }, null, 8, [
9985
- "group-key",
9986
- "applied",
9987
- "merged",
9988
- "op-type",
9989
- "desc",
9990
- "source",
9991
- "time",
9992
- "time-title",
9993
- "step-count",
9994
- "sub-steps",
9995
- "is-current",
10720
+ "group",
9996
10721
  "expanded",
9997
10722
  "goto-enabled"
9998
10723
  ]);
9999
10724
  }), 128)),
10000
- (0, vue.createCommentVNode)("\n 初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。\n 当 bucket 内所有 group 都未 applied 时即为当前位置。\n showInitial=false 时不展示(用于没有\"撤销到初始状态\"语义的自定义历史,如业务模块历史)。\n "),
10001
- __props.showInitial !== false ? ((0, vue.openBlock)(), (0, vue.createBlock)(InitialRow_default, {
10725
+ (0, vue.createCommentVNode)("\n 初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。\n 当 bucket 内所有 group 都未 applied 时即为当前位置。\n config.showInitial=false 时不展示(用于没有\"撤销到初始状态\"语义的自定义历史,如业务模块历史)。\n "),
10726
+ __props.config.showInitial !== false ? ((0, vue.openBlock)(), (0, vue.createBlock)(InitialRow_default, {
10002
10727
  key: 0,
10003
10728
  "is-current": isInitial.value,
10004
- "goto-enabled": __props.gotoEnabled,
10729
+ "goto-enabled": __props.config.gotoEnabled,
10005
10730
  onGotoInitial: _cache[4] || (_cache[4] = ($event) => _ctx.$emit("goto-initial", __props.bucketId))
10006
10731
  }, null, 8, ["is-current", "goto-enabled"])) : (0, vue.createCommentVNode)("v-if", true)
10007
10732
  ])]);
@@ -10017,69 +10742,52 @@
10017
10742
  key: 0,
10018
10743
  class: "m-editor-history-list-empty"
10019
10744
  };
10745
+ var _hoisted_2$20 = { class: "m-editor-history-list-toolbar" };
10746
+ var _hoisted_3$8 = ["title"];
10020
10747
  var BucketTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10021
10748
  name: "MEditorHistoryListBucketTab",
10022
10749
  __name: "BucketTab",
10023
10750
  props: {
10024
- title: {},
10025
- prefix: {},
10751
+ config: {},
10026
10752
  buckets: {},
10027
- describeGroup: {},
10028
- describeStep: {},
10029
- isStepDiffable: {},
10030
- isStepRevertable: {},
10031
- expanded: {},
10032
- gotoEnabled: {
10033
- type: Boolean,
10034
- default: true
10035
- }
10753
+ expanded: {}
10036
10754
  },
10037
10755
  emits: [
10038
10756
  "toggle",
10039
10757
  "goto",
10040
10758
  "goto-initial",
10041
10759
  "diff-step",
10042
- "revert-step"
10760
+ "revert-step",
10761
+ "clear"
10043
10762
  ],
10044
10763
  setup(__props) {
10045
10764
  return (_ctx, _cache) => {
10046
- 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), {
10047
- key: 1,
10048
- "max-height": "360px"
10049
- }, {
10765
+ 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", {
10766
+ class: "m-editor-history-list-clear",
10767
+ title: `清空${__props.config.title}的历史记录`,
10768
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
10769
+ }, "清空", 8, _hoisted_3$8)]), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicScrollbar), { "max-height": "360px" }, {
10050
10770
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.buckets, (bucket) => {
10051
10771
  return (0, vue.openBlock)(), (0, vue.createBlock)(Bucket_default, {
10052
- key: `${__props.prefix}-${bucket.id}`,
10053
- title: __props.title,
10772
+ key: `${__props.config.prefix}-${bucket.id}`,
10773
+ config: __props.config,
10054
10774
  "bucket-id": bucket.id,
10055
- prefix: __props.prefix,
10056
10775
  groups: bucket.groups,
10057
- "describe-group": __props.describeGroup,
10058
- "describe-step": __props.describeStep,
10059
- "is-step-diffable": __props.isStepDiffable,
10060
- "is-step-revertable": __props.isStepRevertable,
10061
10776
  expanded: __props.expanded,
10062
- "goto-enabled": __props.gotoEnabled,
10063
- onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
10064
- onGoto: _cache[1] || (_cache[1] = (id, index) => _ctx.$emit("goto", id, index)),
10065
- onGotoInitial: _cache[2] || (_cache[2] = (id) => _ctx.$emit("goto-initial", id)),
10066
- onDiffStep: _cache[3] || (_cache[3] = (id, index) => _ctx.$emit("diff-step", id, index)),
10067
- onRevertStep: _cache[4] || (_cache[4] = (id, index) => _ctx.$emit("revert-step", id, index))
10777
+ onToggle: _cache[1] || (_cache[1] = (key) => _ctx.$emit("toggle", key)),
10778
+ onGoto: _cache[2] || (_cache[2] = (id, index) => _ctx.$emit("goto", id, index)),
10779
+ onGotoInitial: _cache[3] || (_cache[3] = (id) => _ctx.$emit("goto-initial", id)),
10780
+ onDiffStep: _cache[4] || (_cache[4] = (id, index) => _ctx.$emit("diff-step", id, index)),
10781
+ onRevertStep: _cache[5] || (_cache[5] = (id, index) => _ctx.$emit("revert-step", id, index))
10068
10782
  }, null, 8, [
10069
- "title",
10783
+ "config",
10070
10784
  "bucket-id",
10071
- "prefix",
10072
10785
  "groups",
10073
- "describe-group",
10074
- "describe-step",
10075
- "is-step-diffable",
10076
- "is-step-revertable",
10077
- "expanded",
10078
- "goto-enabled"
10786
+ "expanded"
10079
10787
  ]);
10080
10788
  }), 128))]),
10081
10789
  _: 1
10082
- }));
10790
+ })], 64));
10083
10791
  };
10084
10792
  }
10085
10793
  });
@@ -10184,7 +10892,7 @@
10184
10892
  switch (props.category) {
10185
10893
  case "node":
10186
10894
  if (!props.type) return [];
10187
- return removeStyleDisplayConfig(await propsService.getPropsConfig(props.type));
10895
+ return removeStyleDisplayConfig(await propsService.getPropsConfig(props.type, { node: props.value }));
10188
10896
  case "data-source": return dataSourceService.getFormConfig(props.type || "base");
10189
10897
  case "code-block": return getCodeBlockFormConfig({
10190
10898
  paramColConfig: codeBlockService.getParamsColConfig(),
@@ -10277,7 +10985,7 @@
10277
10985
  key: 0,
10278
10986
  class: "m-editor-history-diff-dialog-notice"
10279
10987
  };
10280
- var _hoisted_3$6 = { class: "m-editor-history-diff-dialog-header" };
10988
+ var _hoisted_3$7 = { class: "m-editor-history-diff-dialog-header" };
10281
10989
  var _hoisted_4$5 = { class: "m-editor-history-diff-dialog-target" };
10282
10990
  var _hoisted_5$2 = { class: "m-editor-history-diff-dialog-controls" };
10283
10991
  var _hoisted_6$2 = { class: "m-editor-history-diff-dialog-legend" };
@@ -10292,6 +11000,7 @@
10292
11000
  extendState: {},
10293
11001
  loadConfig: {},
10294
11002
  width: { default: "900px" },
11003
+ isConfirm: { type: Boolean },
10295
11004
  onConfirm: {},
10296
11005
  selfDiffFieldTypes: {}
10297
11006
  },
@@ -10346,9 +11055,12 @@
10346
11055
  if (mode.value !== "current" || !payload.value) return false;
10347
11056
  return isEqual(payload.value.value, payload.value.currentValue);
10348
11057
  });
11058
+ /** confirm() 的 resolve,仅在「等待用户确认回滚」期间存在 */
11059
+ let confirmResolve = null;
10349
11060
  const onConfirmClick = () => {
10350
- const cb = props.onConfirm;
10351
- cb?.();
11061
+ props.onConfirm?.();
11062
+ confirmResolve?.(true);
11063
+ confirmResolve = null;
10352
11064
  visible.value = false;
10353
11065
  };
10354
11066
  const targetText = (0, vue.computed)(() => {
@@ -10370,21 +11082,41 @@
10370
11082
  viewMode.value = "form";
10371
11083
  visible.value = true;
10372
11084
  };
11085
+ /**
11086
+ * 以 Promise 形式打开确认回滚弹窗:
11087
+ * - 用户点击「确定回滚」时 resolve(true);
11088
+ * - 取消 / 关闭 / 按 Esc 等其他方式关闭弹窗时 resolve(false)。
11089
+ *
11090
+ * 同一时刻只允许一个待确认流程,重复调用会先 resolve(false) 掉上一个。
11091
+ */
11092
+ const confirm = (p) => {
11093
+ confirmResolve?.(false);
11094
+ confirmResolve = null;
11095
+ return new Promise((resolve) => {
11096
+ confirmResolve = resolve;
11097
+ open(p);
11098
+ });
11099
+ };
10373
11100
  const close = () => {
10374
11101
  visible.value = false;
10375
11102
  };
10376
11103
  (0, vue.watch)(visible, (v) => {
10377
- if (!v) payload.value = null;
11104
+ if (!v) {
11105
+ payload.value = null;
11106
+ confirmResolve?.(false);
11107
+ confirmResolve = null;
11108
+ }
10378
11109
  });
10379
11110
  const onClose = () => {
10380
11111
  emit("close");
10381
11112
  };
10382
11113
  __expose({
10383
11114
  open,
11115
+ confirm,
10384
11116
  close
10385
11117
  });
10386
11118
  return (_ctx, _cache) => {
10387
- return (0, vue.openBlock)(), (0, vue.createBlock)(vue.Teleport, { to: "body" }, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicDialog), {
11119
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicDialog), {
10388
11120
  modelValue: visible.value,
10389
11121
  "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => visible.value = $event),
10390
11122
  class: "m-editor-history-diff-dialog",
@@ -10395,7 +11127,7 @@
10395
11127
  width: __props.width,
10396
11128
  onClose
10397
11129
  }, {
10398
- 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), {
11130
+ 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), {
10399
11131
  size: "small",
10400
11132
  onClick: _cache[2] || (_cache[2] = ($event) => visible.value = false)
10401
11133
  }, {
@@ -10416,9 +11148,9 @@
10416
11148
  default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("关闭", -1)])]),
10417
11149
  _: 1
10418
11150
  }))]),
10419
- default: (0, vue.withCtx)(() => [payload.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$57, [
11151
+ default: (0, vue.withCtx)(() => [payload.value && visible.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$57, [
10420
11152
  __props.onConfirm ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$19, "仅回滚有差异的字段")) : (0, vue.createCommentVNode)("v-if", true),
10421
- (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), {
11153
+ (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), {
10422
11154
  modelValue: viewMode.value,
10423
11155
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => viewMode.value = $event),
10424
11156
  size: "small",
@@ -10504,7 +11236,7 @@
10504
11236
  "modelValue",
10505
11237
  "title",
10506
11238
  "width"
10507
- ])]);
11239
+ ]);
10508
11240
  };
10509
11241
  }
10510
11242
  });
@@ -10517,99 +11249,86 @@
10517
11249
  key: 0,
10518
11250
  class: "m-editor-history-list-empty"
10519
11251
  };
10520
- var _hoisted_2$18 = { class: "m-editor-history-list-ul" };
11252
+ var _hoisted_2$18 = {
11253
+ key: 0,
11254
+ class: "m-editor-history-list-toolbar"
11255
+ };
11256
+ var _hoisted_3$6 = { class: "m-editor-history-list-ul" };
10521
11257
  var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10522
11258
  name: "MEditorHistoryListPageTab",
10523
11259
  __name: "PageTab",
10524
11260
  props: {
10525
11261
  list: {},
10526
- expanded: {}
11262
+ expanded: {},
11263
+ marker: {}
10527
11264
  },
10528
11265
  emits: [
10529
11266
  "toggle",
10530
11267
  "goto",
10531
11268
  "goto-initial",
10532
11269
  "diff-step",
10533
- "revert-step"
11270
+ "revert-step",
11271
+ "select",
11272
+ "clear"
10534
11273
  ],
10535
11274
  setup(__props) {
10536
11275
  const props = __props;
10537
11276
  /**
10538
11277
  * 当前 step 是否可查看差异:
10539
11278
  * - 仅 update 操作;
10540
- * - 单节点更新(updatedItems.length === 1),且 oldNode / newNode 都存在。
11279
+ * - 单节点更新(diff.length === 1),且 oldSchema / newSchema 都存在。
10541
11280
  * 多节点更新难以选定单一对比目标,统一不展示差异入口。
10542
11281
  */
10543
11282
  const isPageStepDiffable = (step) => {
10544
11283
  if (step.opType !== "update") return false;
10545
- const items = step.updatedItems ?? [];
11284
+ const items = step.diff ?? [];
10546
11285
  if (items.length !== 1) return false;
10547
- return Boolean(items[0]?.oldNode && items[0]?.newNode);
11286
+ return Boolean(items[0]?.oldSchema && items[0]?.newSchema);
10548
11287
  };
11288
+ /** 页面历史的描述 / 可操作性判定集合,注入给统一的 `toRowGroup`。 */
11289
+ const descriptor = {
11290
+ describeGroup: describePageGroup,
11291
+ describeStep: describePageStep,
11292
+ isStepDiffable: isPageStepDiffable,
11293
+ isStepRevertable: isPageStepRevertable
11294
+ };
11295
+ const rowKey = (group) => `pg-${group.steps[0]?.index}`;
11296
+ const toRow = (group) => toRowGroup(group, rowKey(group), descriptor);
10549
11297
  /**
10550
11298
  * 是否处于"初始状态"——即对应页面历史栈 cursor===0:
10551
- * 当 list 中所有 group 的 applied 都为 false 时即为该状态。
10552
- * 没有任何 group 的情况由外层"暂无操作记录"分支兜底,本计算可以不考虑。
11299
+ * 当 list 中所有 group 的 applied 都为 false 时即为该状态(空列表 `every` 返回 true,
11300
+ * 即仅有 marker、无任何操作记录时也视为处于初始状态)。
10553
11301
  */
10554
- const isInitial = (0, vue.computed)(() => props.list.length > 0 && props.list.every((g) => !g.applied));
11302
+ const isInitial = (0, vue.computed)(() => props.list.every((g) => !g.applied));
10555
11303
  return (_ctx, _cache) => {
10556
- 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), {
10557
- key: 1,
10558
- "max-height": "360px"
10559
- }, {
10560
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("ul", _hoisted_2$18, [
11304
+ 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", {
11305
+ class: "m-editor-history-list-clear",
11306
+ title: "清空当前页面的历史记录",
11307
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
11308
+ }, "清空")])) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicScrollbar), { "max-height": "360px" }, {
11309
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("ul", _hoisted_3$6, [
10561
11310
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.list, (group) => {
10562
11311
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupRow_default, {
10563
- key: `pg-${group.steps[0]?.index}`,
10564
- "group-key": `pg-${group.steps[0]?.index}`,
10565
- applied: group.applied,
10566
- merged: group.steps.length > 1,
10567
- "op-type": group.opType,
10568
- desc: (0, vue.unref)(describePageGroup)(group),
10569
- source: (0, vue.unref)(groupSource)(group),
10570
- time: (0, vue.unref)(formatHistoryTime)((0, vue.unref)(groupTimestamp)(group)),
10571
- "time-title": (0, vue.unref)(formatHistoryFullTime)((0, vue.unref)(groupTimestamp)(group)),
10572
- "step-count": group.steps.length,
10573
- "sub-steps": group.steps.map((s) => ({
10574
- index: s.index,
10575
- applied: s.applied,
10576
- isCurrent: s.isCurrent,
10577
- desc: (0, vue.unref)(describePageStep)(s.step),
10578
- diffable: isPageStepDiffable(s.step),
10579
- revertable: s.applied && (0, vue.unref)(isPageStepRevertable)(s.step),
10580
- source: s.step.source,
10581
- time: (0, vue.unref)(formatHistoryTime)(s.step.timestamp),
10582
- timeTitle: (0, vue.unref)(formatHistoryFullTime)(s.step.timestamp)
10583
- })),
10584
- "is-current": group.isCurrent,
10585
- expanded: !!__props.expanded[`pg-${group.steps[0]?.index}`],
10586
- onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
10587
- onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", index)),
10588
- onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", index)),
10589
- onRevertStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("revert-step", index))
10590
- }, null, 8, [
10591
- "group-key",
10592
- "applied",
10593
- "merged",
10594
- "op-type",
10595
- "desc",
10596
- "source",
10597
- "time",
10598
- "time-title",
10599
- "step-count",
10600
- "sub-steps",
10601
- "is-current",
10602
- "expanded"
10603
- ]);
11312
+ key: rowKey(group),
11313
+ group: toRow(group),
11314
+ expanded: (0, vue.unref)(isHistoryGroupExpanded)(__props.expanded, rowKey(group)),
11315
+ "select-enabled": true,
11316
+ onToggle: _cache[1] || (_cache[1] = (key) => _ctx.$emit("toggle", key)),
11317
+ onGoto: _cache[2] || (_cache[2] = (index) => _ctx.$emit("goto", index)),
11318
+ onDiffStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("diff-step", index)),
11319
+ onRevertStep: _cache[4] || (_cache[4] = (index) => _ctx.$emit("revert-step", index)),
11320
+ onSelect: _cache[5] || (_cache[5] = (index) => _ctx.$emit("select", index))
11321
+ }, null, 8, ["group", "expanded"]);
10604
11322
  }), 128)),
10605
- (0, vue.createCommentVNode)("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n "),
11323
+ (0, vue.createCommentVNode)("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n 设置 root 时生成的「未修改的初始状态」标记(marker)会作为该行的文案与时间来源。\n "),
10606
11324
  (0, vue.createVNode)(InitialRow_default, {
10607
11325
  "is-current": isInitial.value,
10608
- onGotoInitial: _cache[4] || (_cache[4] = ($event) => _ctx.$emit("goto-initial"))
10609
- }, null, 8, ["is-current"])
11326
+ marker: __props.marker,
11327
+ onGotoInitial: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("goto-initial"))
11328
+ }, null, 8, ["is-current", "marker"])
10610
11329
  ])]),
10611
11330
  _: 1
10612
- }));
11331
+ })], 64));
10613
11332
  };
10614
11333
  }
10615
11334
  });
@@ -10619,6 +11338,12 @@
10619
11338
  //#endregion
10620
11339
  //#region packages/editor/src/layouts/history-list/HistoryListPanel.vue?vue&type=script&setup=true&lang.ts
10621
11340
  var _hoisted_1$55 = { class: "m-editor-history-list" };
11341
+ /**
11342
+ * 构造差异弹窗入参:仅 update(前后值都存在)可对比。
11343
+ * - 页面(无 id):在全部分组中按 index 定位 step,目标 id 取自快照;
11344
+ * - 数据源 / 代码块(带 id):先匹配分组 id 再按 index 定位。
11345
+ * 无可对比内容(多节点 / add / remove)或定位不到时返回 null。
11346
+ */
10622
11347
  var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10623
11348
  name: "MEditorHistoryListPanel",
10624
11349
  __name: "HistoryListPanel",
@@ -10626,8 +11351,8 @@
10626
11351
  /**
10627
11352
  * 历史记录面板:在顶部 NavMenu 上点击图标打开 popover,分三个 tab:
10628
11353
  * - 页面:当前活动页面的历史栈,连续修改同一节点的多步会被合并成一组
10629
- * - 数据源:以 dataSource.id 分组,每组内部相邻的连续 update 自动合并
10630
- * - 代码块:同上,按 codeBlock.id 分组并合并相邻 update
11354
+ * - 数据源:以 dataSource.id 分桶,每条操作记录独立展示
11355
+ * - 代码块:同上,按 codeBlock.id 分桶,每条操作记录独立展示
10631
11356
  *
10632
11357
  * 数据通过 historyService 暴露的聚合 API 读取,UI 仅用于只读展示,
10633
11358
  * 同时支持点击任意一条记录跳转至该状态:
@@ -10647,7 +11372,10 @@
10647
11372
  const ClockIcon = (0, vue.markRaw)(_element_plus_icons_vue.Clock);
10648
11373
  const CloseIcon = (0, vue.markRaw)(_element_plus_icons_vue.Close);
10649
11374
  const activeTab = (0, vue.ref)("page");
10650
- /** 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。 */
11375
+ /**
11376
+ * 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。
11377
+ * 点击面板以外区域的自动收起由 TMagicPopover 通过 v-model:visible 回写完成。
11378
+ */
10651
11379
  const visible = (0, vue.ref)(false);
10652
11380
  const tabPaneComponent = (0, _tmagic_design.getDesignConfig)("components")?.tabPane;
10653
11381
  /**
@@ -10657,7 +11385,7 @@
10657
11385
  const extraTabs = (0, vue.inject)("historyListExtraTabs", []);
10658
11386
  /** label 支持字符串或函数,函数形式便于展示动态数量等内容。 */
10659
11387
  const resolveTabLabel = (tab) => typeof tab.label === "function" ? tab.label() : tab.label;
10660
- const { editorService, dataSourceService, codeBlockService, historyService, propsService } = useServices();
11388
+ const { editorService, dataSourceService, codeBlockService, historyService, propsService, stageOverlayService } = useServices();
10661
11389
  /**
10662
11390
  * 数据源 / 代码块功能可被业务方通过 `disabledDataSource` / `disabledCodeBlock` 禁用,
10663
11391
  * 禁用后对应的历史记录 tab 不再展示。若当前激活的 tab 恰好被禁用,则回退到「页面」tab。
@@ -10674,15 +11402,60 @@
10674
11402
  */
10675
11403
  const extendFormState = (0, vue.inject)("extendFormState", void 0);
10676
11404
  const { expanded, toggleGroup, pageGroups, dataSourceGroups, codeBlockGroups, pageGroupsDisplay, dataSourceGroupsByTarget, codeBlockGroupsByTarget } = useHistoryList();
11405
+ /**
11406
+ * 当前活动页的「加载/初始」标记记录(设置 root 时生成),透传给 PageTab 的底部初始行展示。
11407
+ * 基于 historyService 的 reactive state 派生,活动页切换或标记写入后自动刷新。
11408
+ */
11409
+ const pageMarker = (0, vue.computed)(() => historyService.getPageMarker());
10677
11410
  /** 数据源 step 仅 update(前后 schema 都存在)时可查看差异。 */
10678
- const isDataSourceStepDiffable = (step) => Boolean(step.oldSchema && step.newSchema);
11411
+ const isDataSourceStepDiffable = (step) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
10679
11412
  /** 代码块 step 仅 update(前后 content 都存在)时可查看差异。 */
10680
- const isCodeBlockStepDiffable = (step) => Boolean(step.oldContent && step.newContent);
11413
+ const isCodeBlockStepDiffable = (step) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
11414
+ /**
11415
+ * 数据源 / 代码块两类 bucket 历史的整体渲染配置:把 title / prefix 与各自的描述、
11416
+ * 可差异、可回滚判定收敛为单一对象整体注入 BucketTab,组件内部按需读取。
11417
+ */
11418
+ const dataSourceConfig = {
11419
+ title: "数据源",
11420
+ prefix: "ds",
11421
+ describeGroup: describeDataSourceGroup,
11422
+ describeStep: describeDataSourceStep,
11423
+ isStepDiffable: isDataSourceStepDiffable,
11424
+ isStepRevertable: isDataSourceStepRevertable
11425
+ };
11426
+ const codeBlockConfig = {
11427
+ title: "代码块",
11428
+ prefix: "cb",
11429
+ describeGroup: describeCodeBlockGroup,
11430
+ describeStep: describeCodeBlockStep,
11431
+ isStepDiffable: isCodeBlockStepDiffable,
11432
+ isStepRevertable: isCodeBlockStepRevertable
11433
+ };
10681
11434
  /** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
10682
11435
  const indexToCursor = (index) => index + 1;
10683
11436
  const onPageGoto = (index) => {
10684
11437
  editorService.gotoPageStep(indexToCursor(index));
10685
11438
  };
11439
+ /**
11440
+ * 点击页面历史记录行:选中该记录对应的画布节点。
11441
+ * - 从目标 step 的 diff 中取节点 id(优先 newSchema,回退 oldSchema),按出现顺序找到第一个当前仍存在的节点;
11442
+ * - 与图层树点击选中一致:editorService.select + 画布 / overlay 画布 select 三者联动;
11443
+ * - 该 step 涉及的节点都已不存在(如删除记录、被撤销的新增)时给出提示,不做选中。
11444
+ */
11445
+ const onPageSelect = async (index) => {
11446
+ const step = historyService.getPageStepList()[index]?.step;
11447
+ if (!step) return;
11448
+ const targetId = (step.diff ?? []).map((item) => item.newSchema?.id ?? item.oldSchema?.id).find((id) => id !== void 0 && id !== null && editorService.getNodeById(id, false));
11449
+ if (targetId === void 0 || targetId === null) {
11450
+ _tmagic_design.tMagicMessage.warning("该记录对应的节点已不存在,无法选中");
11451
+ return;
11452
+ }
11453
+ const node = editorService.getNodeById(targetId, false);
11454
+ if (!node) return;
11455
+ await editorService.select(node);
11456
+ editorService.get("stage")?.select(targetId);
11457
+ stageOverlayService.get("stage")?.select(targetId);
11458
+ };
10686
11459
  const onDataSourceGoto = (id, index) => {
10687
11460
  dataSourceService.goto(id, indexToCursor(index));
10688
11461
  };
@@ -10703,71 +11476,53 @@
10703
11476
  codeBlockService.goto(id, 0);
10704
11477
  };
10705
11478
  const diffDialogRef = (0, vue.useTemplateRef)("diffDialog");
11479
+ const confirmDialogRef = (0, vue.useTemplateRef)("confirmDialog");
10706
11480
  /**
10707
- * 构造页面 step 的差异弹窗入参:仅 update 单节点修改可对比,传入旧/新节点。
10708
- * 节点类型 `type` 优先取 newNode.type,再回退 oldNode.type。
10709
- * `currentValue` 取自 editorService 中该节点当前实际值,用于支持「与当前对比」。
10710
- * 无可对比内容(如多节点 / add / remove)时返回 null。
11481
+ * 三类历史(页面 / 数据源 / 代码块)差异弹窗入参的构造差异,收敛为一份配置:
11482
+ * 仅「分组来源、当前值读取、类型 / 展示名提取」不同,定位 step、校验前后值、组装 payload 的流程共用。
10711
11483
  */
10712
- const buildPageDiffPayload = (index) => {
10713
- const groups = historyService.getPageHistoryGroups();
10714
- for (const group of groups) {
10715
- const entry = group.steps.find((s) => s.index === index);
10716
- if (!entry) continue;
10717
- const item = entry.step.updatedItems?.[0];
10718
- if (!item?.oldNode || !item?.newNode) return null;
10719
- const type = item.newNode.type || item.oldNode.type || "";
10720
- const nodeId = item.newNode.id ?? item.oldNode.id;
10721
- const currentNode = nodeId !== void 0 ? editorService.getNodeById(nodeId) : null;
11484
+ const buildDiffPayload = (source, index, id) => {
11485
+ for (const group of source.groups()) {
11486
+ if (id !== void 0 && group.id !== id) continue;
11487
+ const step = group.steps.find((s) => s.index === index)?.step;
11488
+ if (!step) continue;
11489
+ const oldSchema = step.diff?.[0]?.oldSchema;
11490
+ const newSchema = step.diff?.[0]?.newSchema;
11491
+ if (!oldSchema || !newSchema) return null;
11492
+ const targetId = id ?? newSchema.id ?? oldSchema.id;
11493
+ const type = source.resolveType?.(newSchema, oldSchema);
10722
11494
  return {
10723
- category: "node",
10724
- type,
10725
- lastValue: item.oldNode,
10726
- value: item.newNode,
10727
- currentValue: currentNode || null,
10728
- targetLabel: item.newNode.name || item.oldNode.name || type,
10729
- id: nodeId
11495
+ category: source.category,
11496
+ ...type !== void 0 ? { type } : {},
11497
+ lastValue: oldSchema,
11498
+ value: newSchema,
11499
+ currentValue: (targetId !== void 0 ? source.getCurrent(targetId) : null) || null,
11500
+ targetLabel: source.resolveLabel(newSchema, oldSchema, targetId),
11501
+ id: targetId
10730
11502
  };
10731
11503
  }
10732
11504
  return null;
10733
11505
  };
10734
- /**
10735
- * 在指定分组列表中按 id / index 查找命中的 step,命中后交由 build 构造差异弹窗入参。
10736
- * 用于统一数据源、代码块两类历史的查找逻辑。
10737
- */
10738
- const findGroupStep = (groups, id, index, build) => {
10739
- for (const group of groups) {
10740
- if (group.id !== id) continue;
10741
- const entry = group.steps.find((s) => s.index === index);
10742
- if (!entry) continue;
10743
- return build(entry.step);
10744
- }
10745
- return null;
10746
- };
10747
- const buildDataSourceDiffPayload = (id, index) => findGroupStep(historyService.getDataSourceHistoryGroups(), id, index, ({ oldSchema, newSchema }) => {
10748
- if (!oldSchema || !newSchema) return null;
10749
- const currentSchema = dataSourceService.getDataSourceById(`${id}`);
10750
- return {
10751
- category: "data-source",
10752
- type: newSchema.type || oldSchema.type || "base",
10753
- lastValue: oldSchema,
10754
- value: newSchema,
10755
- currentValue: currentSchema || null,
10756
- targetLabel: newSchema.title || oldSchema.title || `${id}`,
10757
- id
10758
- };
10759
- });
10760
- const buildCodeBlockDiffPayload = (id, index) => findGroupStep(historyService.getCodeBlockHistoryGroups(), id, index, ({ oldContent, newContent }) => {
10761
- if (!oldContent || !newContent) return null;
10762
- return {
10763
- category: "code-block",
10764
- lastValue: oldContent,
10765
- value: newContent,
10766
- currentValue: codeBlockService.getCodeContentById(id) || null,
10767
- targetLabel: newContent.name || oldContent.name || `${id}`,
10768
- id
10769
- };
10770
- });
11506
+ const buildPageDiffPayload = (index) => buildDiffPayload({
11507
+ category: "node",
11508
+ groups: () => historyService.getPageHistoryGroups(),
11509
+ getCurrent: (id) => editorService.getNodeById(id),
11510
+ resolveType: (n, o) => n.type || o.type || "",
11511
+ resolveLabel: (n, o) => n.name || o.name || n.type || o.type || ""
11512
+ }, index);
11513
+ const buildDataSourceDiffPayload = (id, index) => buildDiffPayload({
11514
+ category: "data-source",
11515
+ groups: () => historyService.getDataSourceHistoryGroups(),
11516
+ getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`),
11517
+ resolveType: (n, o) => n.type || o.type || "base",
11518
+ resolveLabel: (n, o, id) => n.title || o.title || `${id}`
11519
+ }, index, id);
11520
+ const buildCodeBlockDiffPayload = (id, index) => buildDiffPayload({
11521
+ category: "code-block",
11522
+ groups: () => historyService.getCodeBlockHistoryGroups(),
11523
+ getCurrent: (id) => codeBlockService.getCodeContentById(id),
11524
+ resolveLabel: (n, o, id) => n.name || o.name || `${id}`
11525
+ }, index, id);
10771
11526
  const onPageDiff = (index) => {
10772
11527
  const payload = buildPageDiffPayload(index);
10773
11528
  if (payload) diffDialogRef.value?.open(payload);
@@ -10780,173 +11535,259 @@
10780
11535
  const payload = buildCodeBlockDiffPayload(id, index);
10781
11536
  if (payload) diffDialogRef.value?.open(payload);
10782
11537
  };
10783
- const onConfirmRevert = (0, vue.shallowRef)();
10784
11538
  /**
10785
- * 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
11539
+ * 「回滚」统一入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
10786
11540
  * 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
10787
11541
  *
10788
- * 交互:先弹出该步骤的差异弹窗供用户确认,点击「确定回滚」后再真正执行回滚;
10789
- * 对没有可对比内容的步骤(如 add / remove / 多节点更新)则直接回滚。
11542
+ * 交互:
11543
+ * - 可差异对比的步骤(单节点 / 单实体 update):弹出差异弹窗供用户确认,点「确定回滚」再执行;
11544
+ * - 无法对比的步骤(add / remove / 多节点更新,payload 为 null):弹出普通二次确认框,确认后执行。
11545
+ *
11546
+ * 页面 / 数据源 / 代码块三类回滚仅「差异入参构造」与「实际 revert 调用」不同,
11547
+ * 由调用方分别传入 payload 与 revert,公共的弹窗 / 确认流程在此收敛。
11548
+ */
11549
+ const runRevert = (payload) => {
11550
+ if (payload && confirmDialogRef.value) return confirmDialogRef.value.confirm(payload);
11551
+ return confirmRevert();
11552
+ };
11553
+ /**
11554
+ * 回滚前置校验:若该历史步骤回滚所依赖的目标数据已被删除,则无法回滚。
11555
+ * - update(把旧值写回):被修改的目标必须仍存在;
11556
+ * - 页面 remove(还原被删节点):被删节点的原父容器必须仍存在,否则无处插回;
11557
+ * add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。
11558
+ *
11559
+ * 命中时弹出「回滚失败」提示并返回 true,调用方据此中止本次回滚。
10790
11560
  */
11561
+ const isPageRevertTargetMissing = (index) => {
11562
+ const step = historyService.getPageStepList()[index]?.step;
11563
+ if (!step) return false;
11564
+ if (step.opType === "update") return (step.diff ?? []).some((item) => {
11565
+ const id = item.newSchema?.id ?? item.oldSchema?.id;
11566
+ return id !== void 0 && !editorService.getNodeById(id, false);
11567
+ });
11568
+ if (step.opType === "remove") return (step.diff ?? []).some((item) => item.parentId !== void 0 && !editorService.getNodeById(item.parentId, false));
11569
+ return false;
11570
+ };
11571
+ /** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */
11572
+ const isDataSourceRevertTargetMissing = (id, index) => {
11573
+ const step = historyService.getDataSourceStepList(id)[index]?.step;
11574
+ return Boolean(step && step.opType === "update" && !dataSourceService.getDataSourceById(`${id}`));
11575
+ };
11576
+ /** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */
11577
+ const isCodeBlockRevertTargetMissing = (id, index) => {
11578
+ const step = historyService.getCodeBlockStepList(id)[index]?.step;
11579
+ return Boolean(step && step.opType === "update" && !codeBlockService.getCodeContentById(id));
11580
+ };
11581
+ /** 目标数据已被删除、无法回滚时的统一提示。 */
11582
+ const showRevertTargetMissing = () => {
11583
+ _tmagic_design.tMagicMessage.error("回滚失败:该记录对应的数据已被删除");
11584
+ };
10791
11585
  const onPageRevert = (index) => {
10792
- const payload = buildPageDiffPayload(index);
10793
- onConfirmRevert.value = () => editorService.revertPageStep(index);
10794
- if (payload) diffDialogRef.value?.open({ ...payload });
10795
- else onConfirmRevert.value();
11586
+ if (isPageRevertTargetMissing(index)) {
11587
+ showRevertTargetMissing();
11588
+ return Promise.resolve(null);
11589
+ }
11590
+ return runRevert(buildPageDiffPayload(index)).then((result) => result ? editorService.revertPageStep(index) : null);
10796
11591
  };
10797
11592
  const onDataSourceRevert = (id, index) => {
10798
- const payload = buildDataSourceDiffPayload(id, index);
10799
- onConfirmRevert.value = () => dataSourceService.revert(id, index);
10800
- if (payload) diffDialogRef.value?.open({ ...payload });
10801
- else onConfirmRevert.value();
11593
+ if (isDataSourceRevertTargetMissing(id, index)) {
11594
+ showRevertTargetMissing();
11595
+ return Promise.resolve(null);
11596
+ }
11597
+ return runRevert(buildDataSourceDiffPayload(id, index)).then((result) => result ? dataSourceService.revert(id, index) : null);
10802
11598
  };
10803
11599
  const onCodeBlockRevert = (id, index) => {
10804
- const payload = buildCodeBlockDiffPayload(id, index);
10805
- onConfirmRevert.value = () => codeBlockService.revert(id, index);
10806
- if (payload) diffDialogRef.value?.open({ ...payload });
10807
- else onConfirmRevert.value();
11600
+ if (isCodeBlockRevertTargetMissing(id, index)) {
11601
+ showRevertTargetMissing();
11602
+ return Promise.resolve(null);
11603
+ }
11604
+ return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) => result ? codeBlockService.revert(id, index) : null);
11605
+ };
11606
+ /**
11607
+ * 「回滚」二次确认:新增 / 删除 / 多节点更新等无法做差异对比的步骤,
11608
+ * 不弹差异弹窗,改用一个普通确认框替代「确定回滚」按钮,避免点击后无任何提示直接执行。
11609
+ * 用户取消时返回 false,调用方据此中止回滚。
11610
+ */
11611
+ const confirmRevert = () => confirmDialog("确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。");
11612
+ /**
11613
+ * 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
11614
+ * 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
11615
+ */
11616
+ const confirmDialog = async (message) => {
11617
+ try {
11618
+ await _tmagic_design.tMagicMessageBox.confirm(message, "提示", {
11619
+ confirmButtonText: "确定",
11620
+ cancelButtonText: "取消",
11621
+ type: "warning"
11622
+ });
11623
+ return true;
11624
+ } catch (e) {
11625
+ return false;
11626
+ }
11627
+ };
11628
+ /**
11629
+ * 把内存中(已清空对应类别后的)历史状态重新写回 IndexedDB,
11630
+ * 使本地持久化的那份与内存保持一致——即「连同本地保存的一并删除」。
11631
+ * 不支持 IndexedDB 或写入失败时静默忽略(内存清空已生效)。
11632
+ */
11633
+ const syncIndexedDB = async () => {
11634
+ try {
11635
+ await historyService.saveToIndexedDB();
11636
+ } catch (e) {}
11637
+ };
11638
+ const onPageClear = async () => {
11639
+ if (await confirmDialog("确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11640
+ historyService.clearPage();
11641
+ await syncIndexedDB();
11642
+ }
11643
+ };
11644
+ const onDataSourceClear = async () => {
11645
+ if (await confirmDialog("确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11646
+ historyService.clearDataSource();
11647
+ await syncIndexedDB();
11648
+ }
10808
11649
  };
10809
- const onDiffDialogClose = () => {
10810
- onConfirmRevert.value = void 0;
11650
+ const onCodeBlockClear = async () => {
11651
+ if (await confirmDialog("确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。")) {
11652
+ historyService.clearCodeBlock();
11653
+ await syncIndexedDB();
11654
+ }
10811
11655
  };
10812
11656
  return (_ctx, _cache) => {
10813
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
10814
- "popper-class": "m-editor-history-list-popover",
10815
- placement: "bottom",
10816
- trigger: "click",
10817
- visible: visible.value,
10818
- width: 660
10819
- }, {
10820
- reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
10821
- effect: "dark",
11657
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [
11658
+ (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicPopover), {
11659
+ "popper-class": "m-editor-history-list-popover",
10822
11660
  placement: "bottom",
10823
- content: "历史记录"
11661
+ trigger: "click",
11662
+ visible: visible.value,
11663
+ "onUpdate:visible": _cache[3] || (_cache[3] = ($event) => visible.value = $event),
11664
+ width: 660
10824
11665
  }, {
10825
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
10826
- size: "small",
10827
- link: "",
10828
- onClick: _cache[2] || (_cache[2] = ($event) => visible.value = !visible.value)
11666
+ reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
11667
+ effect: "dark",
11668
+ placement: "bottom",
11669
+ content: "历史记录"
10829
11670
  }, {
10830
- icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(ClockIcon) }, null, 8, ["icon"])]),
11671
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
11672
+ size: "small",
11673
+ link: "",
11674
+ onClick: _cache[2] || (_cache[2] = ($event) => visible.value = !visible.value)
11675
+ }, {
11676
+ icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(ClockIcon) }, null, 8, ["icon"])]),
11677
+ _: 1
11678
+ })]),
10831
11679
  _: 1
10832
11680
  })]),
10833
- _: 1
10834
- })]),
10835
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$55, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
10836
- effect: "dark",
10837
- placement: "top",
10838
- content: "关闭"
10839
- }, {
10840
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
10841
- class: "m-editor-history-list-close",
10842
- size: "small",
10843
- link: "",
10844
- onClick: _cache[0] || (_cache[0] = ($event) => visible.value = false)
11681
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$55, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
11682
+ effect: "dark",
11683
+ placement: "top",
11684
+ content: "关闭"
10845
11685
  }, {
10846
- icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(CloseIcon) }, null, 8, ["icon"])]),
10847
- _: 1
10848
- })]),
10849
- _: 1
10850
- }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTabs), {
10851
- modelValue: activeTab.value,
10852
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => activeTab.value = $event),
10853
- class: "m-editor-history-list-tabs"
10854
- }, {
10855
- default: (0, vue.withCtx)(() => [
10856
- ((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({
10857
- name: "page",
10858
- label: `页面 (${(0, vue.unref)(pageGroups).length})`
10859
- }) || {})), {
10860
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(PageTab_default, {
10861
- list: (0, vue.unref)(pageGroupsDisplay),
10862
- expanded: (0, vue.unref)(expanded),
10863
- onToggle: (0, vue.unref)(toggleGroup),
10864
- onGoto: onPageGoto,
10865
- onGotoInitial: onPageGotoInitial,
10866
- onDiffStep: onPageDiff,
10867
- onRevertStep: onPageRevert
10868
- }, null, 8, [
10869
- "list",
10870
- "expanded",
10871
- "onToggle"
10872
- ])]),
10873
- _: 1
10874
- }, 16)),
10875
- !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({
10876
- name: "data-source",
10877
- label: `数据源 (${(0, vue.unref)(dataSourceGroups).length})`
10878
- }) || {})), {
10879
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
10880
- title: "数据源",
10881
- prefix: "ds",
10882
- buckets: (0, vue.unref)(dataSourceGroupsByTarget),
10883
- expanded: (0, vue.unref)(expanded),
10884
- "describe-group": (0, vue.unref)(describeDataSourceGroup),
10885
- "describe-step": (0, vue.unref)(describeDataSourceStep),
10886
- "is-step-diffable": isDataSourceStepDiffable,
10887
- "is-step-revertable": (0, vue.unref)(isDataSourceStepRevertable),
10888
- onToggle: (0, vue.unref)(toggleGroup),
10889
- onGoto: onDataSourceGoto,
10890
- onGotoInitial: onDataSourceGotoInitial,
10891
- onDiffStep: onDataSourceDiff,
10892
- onRevertStep: onDataSourceRevert
10893
- }, null, 8, [
10894
- "buckets",
10895
- "expanded",
10896
- "describe-group",
10897
- "describe-step",
10898
- "is-step-revertable",
10899
- "onToggle"
10900
- ])]),
10901
- _: 1
10902
- }, 16)) : (0, vue.createCommentVNode)("v-if", true),
10903
- !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({
10904
- name: "code-block",
10905
- label: `代码块 (${(0, vue.unref)(codeBlockGroups).length})`
10906
- }) || {})), {
10907
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
10908
- title: "代码块",
10909
- prefix: "cb",
10910
- buckets: (0, vue.unref)(codeBlockGroupsByTarget),
10911
- expanded: (0, vue.unref)(expanded),
10912
- "describe-group": (0, vue.unref)(describeCodeBlockGroup),
10913
- "describe-step": (0, vue.unref)(describeCodeBlockStep),
10914
- "is-step-diffable": isCodeBlockStepDiffable,
10915
- "is-step-revertable": (0, vue.unref)(isCodeBlockStepRevertable),
10916
- onToggle: (0, vue.unref)(toggleGroup),
10917
- onGoto: onCodeBlockGoto,
10918
- onGotoInitial: onCodeBlockGotoInitial,
10919
- onDiffStep: onCodeBlockDiff,
10920
- onRevertStep: onCodeBlockRevert
10921
- }, null, 8, [
10922
- "buckets",
10923
- "expanded",
10924
- "describe-group",
10925
- "describe-step",
10926
- "is-step-revertable",
10927
- "onToggle"
10928
- ])]),
11686
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
11687
+ class: "m-editor-history-list-close",
11688
+ size: "small",
11689
+ link: "",
11690
+ onClick: _cache[0] || (_cache[0] = ($event) => visible.value = false)
11691
+ }, {
11692
+ icon: (0, vue.withCtx)(() => [(0, vue.createVNode)(Icon_default, { icon: (0, vue.unref)(CloseIcon) }, null, 8, ["icon"])]),
10929
11693
  _: 1
10930
- }, 16)) : (0, vue.createCommentVNode)("v-if", true),
10931
- ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)((0, vue.unref)(extraTabs), (tab) => {
10932
- 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({
10933
- name: tab.name,
10934
- label: resolveTabLabel(tab)
10935
- }) || {}), {
10936
- 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))]),
10937
- _: 2
10938
- }, 1040);
10939
- }), 128))
10940
- ]),
11694
+ })]),
11695
+ _: 1
11696
+ }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTabs), {
11697
+ modelValue: activeTab.value,
11698
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => activeTab.value = $event),
11699
+ class: "m-editor-history-list-tabs"
11700
+ }, {
11701
+ default: (0, vue.withCtx)(() => [
11702
+ ((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({
11703
+ name: "page",
11704
+ label: `页面 (${(0, vue.unref)(pageGroups).length})`
11705
+ }) || {})), {
11706
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(PageTab_default, {
11707
+ list: (0, vue.unref)(pageGroupsDisplay),
11708
+ expanded: (0, vue.unref)(expanded),
11709
+ marker: pageMarker.value,
11710
+ onToggle: (0, vue.unref)(toggleGroup),
11711
+ onGoto: onPageGoto,
11712
+ onGotoInitial: onPageGotoInitial,
11713
+ onDiffStep: onPageDiff,
11714
+ onRevertStep: onPageRevert,
11715
+ onSelect: onPageSelect,
11716
+ onClear: onPageClear
11717
+ }, null, 8, [
11718
+ "list",
11719
+ "expanded",
11720
+ "marker",
11721
+ "onToggle"
11722
+ ])]),
11723
+ _: 1
11724
+ }, 16)),
11725
+ !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({
11726
+ name: "data-source",
11727
+ label: `数据源 (${(0, vue.unref)(dataSourceGroups).length})`
11728
+ }) || {})), {
11729
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
11730
+ config: dataSourceConfig,
11731
+ buckets: (0, vue.unref)(dataSourceGroupsByTarget),
11732
+ expanded: (0, vue.unref)(expanded),
11733
+ onToggle: (0, vue.unref)(toggleGroup),
11734
+ onGoto: onDataSourceGoto,
11735
+ onGotoInitial: onDataSourceGotoInitial,
11736
+ onDiffStep: onDataSourceDiff,
11737
+ onRevertStep: onDataSourceRevert,
11738
+ onClear: onDataSourceClear
11739
+ }, null, 8, [
11740
+ "buckets",
11741
+ "expanded",
11742
+ "onToggle"
11743
+ ])]),
11744
+ _: 1
11745
+ }, 16)) : (0, vue.createCommentVNode)("v-if", true),
11746
+ !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({
11747
+ name: "code-block",
11748
+ label: `代码块 (${(0, vue.unref)(codeBlockGroups).length})`
11749
+ }) || {})), {
11750
+ default: (0, vue.withCtx)(() => [(0, vue.createVNode)(BucketTab_default, {
11751
+ config: codeBlockConfig,
11752
+ buckets: (0, vue.unref)(codeBlockGroupsByTarget),
11753
+ expanded: (0, vue.unref)(expanded),
11754
+ onToggle: (0, vue.unref)(toggleGroup),
11755
+ onGoto: onCodeBlockGoto,
11756
+ onGotoInitial: onCodeBlockGotoInitial,
11757
+ onDiffStep: onCodeBlockDiff,
11758
+ onRevertStep: onCodeBlockRevert,
11759
+ onClear: onCodeBlockClear
11760
+ }, null, 8, [
11761
+ "buckets",
11762
+ "expanded",
11763
+ "onToggle"
11764
+ ])]),
11765
+ _: 1
11766
+ }, 16)) : (0, vue.createCommentVNode)("v-if", true),
11767
+ ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)((0, vue.unref)(extraTabs), (tab) => {
11768
+ 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({
11769
+ name: tab.name,
11770
+ label: resolveTabLabel(tab)
11771
+ }) || {}), {
11772
+ 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))]),
11773
+ _: 2
11774
+ }, 1040);
11775
+ }), 128))
11776
+ ]),
11777
+ _: 1
11778
+ }, 8, ["modelValue"])])]),
10941
11779
  _: 1
10942
- }, 8, ["modelValue"])])]),
10943
- _: 1
10944
- }, 8, ["visible"]), (0, vue.createVNode)(HistoryDiffDialog_default, {
10945
- ref: "diffDialog",
10946
- "extend-state": (0, vue.unref)(extendFormState),
10947
- "on-confirm": onConfirmRevert.value,
10948
- onClose: onDiffDialogClose
10949
- }, null, 8, ["extend-state", "on-confirm"])], 64);
11780
+ }, 8, ["visible"]),
11781
+ (0, vue.createVNode)(HistoryDiffDialog_default, {
11782
+ ref: "diffDialog",
11783
+ "extend-state": (0, vue.unref)(extendFormState)
11784
+ }, null, 8, ["extend-state"]),
11785
+ (0, vue.createVNode)(HistoryDiffDialog_default, {
11786
+ ref: "confirmDialog",
11787
+ "is-confirm": true,
11788
+ "extend-state": (0, vue.unref)(extendFormState)
11789
+ }, null, 8, ["extend-state"])
11790
+ ], 64);
10950
11791
  };
10951
11792
  }
10952
11793
  });
@@ -12855,7 +13696,9 @@
12855
13696
  props: /* @__PURE__ */ (0, vue.mergeModels)({
12856
13697
  title: {},
12857
13698
  values: {},
12858
- disabled: { type: Boolean }
13699
+ disabled: { type: Boolean },
13700
+ editMethodName: {},
13701
+ editFieldPath: {}
12859
13702
  }, {
12860
13703
  "visible": {
12861
13704
  type: Boolean,
@@ -12880,9 +13723,20 @@
12880
13723
  const dataSourceConfig = (0, vue.ref)([]);
12881
13724
  const { height: editorHeight } = useEditorContentHeight();
12882
13725
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, (0, vue.inject)("parentFloating", (0, vue.ref)(null)));
13726
+ /** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
13727
+ (0, vue.provide)("editingDataSourceMethodName", (0, vue.computed)(() => props.editMethodName));
13728
+ /** 供「数据定义」tab 内的字段消费,用于打开数据源详情后自动打开指定字段 */
13729
+ (0, vue.provide)("editingDataSourceFieldPath", (0, vue.computed)(() => props.editFieldPath || []));
12883
13730
  (0, vue.watchEffect)(() => {
12884
13731
  initValues.value = props.values;
12885
- dataSourceConfig.value = dataSourceService.getFormConfig(initValues.value.type);
13732
+ const config = dataSourceService.getFormConfig(initValues.value.type);
13733
+ let activeTab = "";
13734
+ if (props.editMethodName) activeTab = "methods";
13735
+ else if (props.editFieldPath?.length) activeTab = "fields";
13736
+ dataSourceConfig.value = activeTab ? config.map((item) => item.type === "tab" ? {
13737
+ ...item,
13738
+ active: activeTab
13739
+ } : item) : config;
12886
13740
  });
12887
13741
  const submitHandler = (values, data) => {
12888
13742
  emit("submit", values, data);
@@ -13171,7 +14025,13 @@
13171
14025
  const eventBus = (0, vue.inject)("eventBus");
13172
14026
  const { dataSourceService } = useServices();
13173
14027
  const { editDialog, dataSourceValues, dialogTitle, editable, editHandler, submitDataSourceHandler } = useDataSourceEdit(dataSourceService);
14028
+ /** 打开数据源详情时需要直接定位并打开的方法名,为空则正常展示「数据定义」tab */
14029
+ const editMethodName = (0, vue.ref)("");
14030
+ /** 打开数据源详情时需要直接定位并打开的字段路径,为空则不自动打开字段配置 */
14031
+ const editFieldPath = (0, vue.ref)([]);
13174
14032
  const editDialogCloseHandler = () => {
14033
+ editMethodName.value = "";
14034
+ editFieldPath.value = [];
13175
14035
  if (dataSourceListRef.value) for (const [, status] of dataSourceListRef.value.nodeStatusMap.entries()) status.selected = false;
13176
14036
  };
13177
14037
  (0, vue.watch)(dataSourceValues, (dataSourceValues) => {
@@ -13209,6 +14069,18 @@
13209
14069
  dataSourceListRef.value?.filter(val);
13210
14070
  };
13211
14071
  eventBus?.on("edit-data-source", (id) => {
14072
+ editMethodName.value = "";
14073
+ editFieldPath.value = [];
14074
+ editHandler(id);
14075
+ });
14076
+ eventBus?.on("edit-data-source-method", (id, methodName) => {
14077
+ editMethodName.value = methodName;
14078
+ editFieldPath.value = [];
14079
+ editHandler(id);
14080
+ });
14081
+ eventBus?.on("edit-data-source-field", (id, fieldPath) => {
14082
+ editMethodName.value = "";
14083
+ editFieldPath.value = fieldPath;
13212
14084
  editHandler(id);
13213
14085
  });
13214
14086
  eventBus?.on("remove-data-source", (id) => {
@@ -13257,12 +14129,16 @@
13257
14129
  disabled: !(0, vue.unref)(editable),
13258
14130
  values: (0, vue.unref)(dataSourceValues),
13259
14131
  title: (0, vue.unref)(dialogTitle),
14132
+ "edit-method-name": editMethodName.value,
14133
+ "edit-field-path": editFieldPath.value,
13260
14134
  onSubmit: (0, vue.unref)(submitDataSourceHandler),
13261
14135
  onClose: editDialogCloseHandler
13262
14136
  }, null, 8, [
13263
14137
  "disabled",
13264
14138
  "values",
13265
14139
  "title",
14140
+ "edit-method-name",
14141
+ "edit-field-path",
13266
14142
  "onSubmit"
13267
14143
  ]),
13268
14144
  ((0, vue.openBlock)(), (0, vue.createBlock)(vue.Teleport, { to: "body" }, [menuData.value.length ? ((0, vue.openBlock)(), (0, vue.createBlock)(ContentMenu_default, {
@@ -14242,7 +15118,11 @@
14242
15118
  });
14243
15119
  }
14244
15120
  }, { immediate: true });
14245
- const activeTabName = (0, vue.ref)(props.data?.status);
15121
+ const activeTabName = (0, vue.computed)({
15122
+ get: () => uiService.get("sideBarActiveTabName"),
15123
+ set: (value) => uiService.set("sideBarActiveTabName", value)
15124
+ });
15125
+ uiService.set("sideBarActiveTabName", props.data?.status || "");
14246
15126
  const getItemConfig = (data) => {
14247
15127
  const map = {
14248
15128
  [SideItemKey.COMPONENT_LIST]: {
@@ -15436,17 +16316,6 @@
15436
16316
  ],
15437
16317
  sync: ["setCodeDslByIdSync"]
15438
16318
  };
15439
- /**
15440
- * 「回滚」生成的新 step 简短描述。仅 service 层使用。
15441
- */
15442
- var describeRevertCodeBlockStep = (step) => {
15443
- const { oldContent, newContent, changeRecords, id } = step;
15444
- if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
15445
- if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
15446
- const name = newContent?.name || oldContent?.name || `${id}`;
15447
- const propPath = changeRecords?.[0]?.propPath;
15448
- return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
15449
- };
15450
16319
  var CodeBlock = class extends BaseService {
15451
16320
  state = (0, vue.reactive)({
15452
16321
  codeDsl: null,
@@ -15455,6 +16324,16 @@
15455
16324
  undeletableList: [],
15456
16325
  paramsColConfig: void 0
15457
16326
  });
16327
+ /**
16328
+ * 最近一次写入历史栈的代码块历史记录 uuid(单条写入场景:新增 / 更新)。
16329
+ * 供 setCodeDslById(Sync)AndGetHistoryId 取回,普通方法不读取它。
16330
+ */
16331
+ lastPushedHistoryId = null;
16332
+ /**
16333
+ * deleteCodeDslByIds 一次删除多个代码块时,按写入顺序收集的历史记录 uuid 列表。
16334
+ * 在 deleteCodeDslByIds 入口处重置,供 deleteCodeDslByIdsAndGetHistoryId 取回。
16335
+ */
16336
+ lastDeletedHistoryIds = [];
15458
16337
  constructor() {
15459
16338
  super([...canUsePluginMethods$4.async.map((methodName) => ({
15460
16339
  name: methodName,
@@ -15537,13 +16416,13 @@
15537
16416
  ...codeConfigProcessed
15538
16417
  };
15539
16418
  const newContent = cloneDeep$1(codeDsl[id]);
15540
- if (!doNotPushHistory) history_default.pushCodeBlock(id, {
16419
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushCodeBlock(id, {
15541
16420
  oldContent,
15542
16421
  newContent,
15543
16422
  changeRecords,
15544
16423
  historyDescription,
15545
16424
  source: historySource
15546
- });
16425
+ })?.uuid ?? null;
15547
16426
  this.emit("addOrUpdate", id, codeDsl[id]);
15548
16427
  }
15549
16428
  /**
@@ -15626,18 +16505,59 @@
15626
16505
  async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription, historySource } = {}) {
15627
16506
  const currentDsl = await this.getCodeDsl();
15628
16507
  if (!currentDsl) return;
16508
+ this.lastDeletedHistoryIds = [];
15629
16509
  codeIds.forEach((id) => {
15630
16510
  const oldContent = currentDsl[id] ? cloneDeep$1(currentDsl[id]) : null;
15631
16511
  delete currentDsl[id];
15632
- if (oldContent && !doNotPushHistory) history_default.pushCodeBlock(id, {
15633
- oldContent,
15634
- newContent: null,
15635
- historyDescription,
15636
- source: historySource
15637
- });
16512
+ if (oldContent && !doNotPushHistory) {
16513
+ const uuid = history_default.pushCodeBlock(id, {
16514
+ oldContent,
16515
+ newContent: null,
16516
+ historyDescription,
16517
+ source: historySource
16518
+ })?.uuid;
16519
+ if (uuid) this.lastDeletedHistoryIds.push(uuid);
16520
+ }
15638
16521
  this.emit("remove", id);
15639
16522
  });
15640
16523
  }
16524
+ /**
16525
+ * 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
16526
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入的历史 uuid 列表,
16527
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16528
+ *
16529
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
16530
+ */
16531
+ /** 等价于 {@link setCodeDslById},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16532
+ async setCodeDslByIdAndGetHistoryId(id, codeConfig, options = {}) {
16533
+ this.lastPushedHistoryId = null;
16534
+ await this.setCodeDslById(id, codeConfig, options);
16535
+ return {
16536
+ result: void 0,
16537
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16538
+ };
16539
+ }
16540
+ /** 等价于 {@link setCodeDslByIdSync},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16541
+ setCodeDslByIdSyncAndGetHistoryId(id, codeConfig, force = true, options = {}) {
16542
+ this.lastPushedHistoryId = null;
16543
+ this.setCodeDslByIdSync(id, codeConfig, force, options);
16544
+ return {
16545
+ result: void 0,
16546
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16547
+ };
16548
+ }
16549
+ /**
16550
+ * 等价于 {@link deleteCodeDslByIds},并额外返回本次写入的全部历史记录 uuid(按删除顺序)。
16551
+ * 一次删除多个代码块会产生多条历史记录;未写入任何历史时 historyIds 为 `[]`。
16552
+ */
16553
+ async deleteCodeDslByIdsAndGetHistoryId(codeIds, options = {}) {
16554
+ this.lastDeletedHistoryIds = [];
16555
+ await this.deleteCodeDslByIds(codeIds, options);
16556
+ return {
16557
+ result: void 0,
16558
+ historyIds: [...this.lastDeletedHistoryIds]
16559
+ };
16560
+ }
15641
16561
  setParamsColConfig(config) {
15642
16562
  this.state.paramsColConfig = config;
15643
16563
  }
@@ -15713,11 +16633,27 @@
15713
16633
  async revert(id, index) {
15714
16634
  const entry = history_default.getCodeBlockStepList(id)[index];
15715
16635
  if (!entry?.applied) return null;
15716
- if (entry.step.oldContent && entry.step.newContent && !entry.step.changeRecords?.length) return null;
15717
- const description = `回滚 #${index + 1}: ${describeRevertCodeBlockStep(entry.step)}`;
16636
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
16637
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
16638
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.name)}`;
15718
16639
  return await this.applyRevertStep(entry.step, description);
15719
16640
  }
15720
16641
  /**
16642
+ * 通过历史记录 uuid 回滚代码块历史步骤,语义同 {@link revert},
16643
+ * 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
16644
+ * 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
16645
+ *
16646
+ * @param uuids 目标历史记录的 uuid 列表,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回的 `historyIds`
16647
+ */
16648
+ async revertById(uuids) {
16649
+ const results = [];
16650
+ for (const uuid of uuids) {
16651
+ const location = history_default.findCodeBlockStepLocationByUuid(uuid);
16652
+ results.push(location ? await this.revert(location.id, location.index) : null);
16653
+ }
16654
+ return results;
16655
+ }
16656
+ /**
15721
16657
  * 生成代码块唯一id
15722
16658
  * @returns {Id} 代码块唯一id
15723
16659
  */
@@ -15782,22 +16718,23 @@
15782
16718
  * 差异仅在于通过公开的 setCodeDslByIdSync / deleteCodeDslByIds 触发 push。
15783
16719
  */
15784
16720
  async applyRevertStep(step, historyDescription) {
15785
- const { id, oldContent, newContent, changeRecords } = step;
15786
- if (oldContent === null && newContent) {
16721
+ const { id } = step;
16722
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
16723
+ if (!oldSchema && newSchema) {
15787
16724
  await this.deleteCodeDslByIds([id], {
15788
16725
  historyDescription,
15789
16726
  historySource: "rollback"
15790
16727
  });
15791
16728
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15792
16729
  }
15793
- if (oldContent && newContent === null) {
15794
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, {
16730
+ if (oldSchema && !newSchema) {
16731
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
15795
16732
  historyDescription,
15796
16733
  historySource: "rollback"
15797
16734
  });
15798
16735
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15799
16736
  }
15800
- if (!oldContent || !newContent) return null;
16737
+ if (!oldSchema || !newSchema) return null;
15801
16738
  if (changeRecords?.length) {
15802
16739
  const current = this.getCodeContentById(id);
15803
16740
  if (!current) return null;
@@ -15808,17 +16745,17 @@
15808
16745
  fallbackToFullReplace = true;
15809
16746
  break;
15810
16747
  }
15811
- const value = cloneDeep$1((0, _tmagic_utils.getValueByKeyPath)(record.propPath, oldContent));
16748
+ const value = cloneDeep$1((0, _tmagic_utils.getValueByKeyPath)(record.propPath, oldSchema));
15812
16749
  (0, _tmagic_utils.setValueByKeyPath)(record.propPath, value, patched);
15813
16750
  }
15814
- this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldContent) : patched, true, {
16751
+ this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, true, {
15815
16752
  changeRecords,
15816
16753
  historyDescription,
15817
16754
  historySource: "rollback"
15818
16755
  });
15819
16756
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
15820
16757
  }
15821
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, {
16758
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
15822
16759
  historyDescription,
15823
16760
  historySource: "rollback"
15824
16761
  });
@@ -15839,19 +16776,20 @@
15839
16776
  * @param reverse true=撤销,false=重做
15840
16777
  */
15841
16778
  async applyHistoryStep(step, reverse) {
15842
- const { id, oldContent, newContent, changeRecords } = step;
15843
- if (oldContent === null && newContent) {
16779
+ const { id } = step;
16780
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
16781
+ if (!oldSchema && newSchema) {
15844
16782
  if (reverse) await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
15845
- else this.setCodeDslByIdSync(id, cloneDeep$1(newContent), true, { doNotPushHistory: true });
16783
+ else this.setCodeDslByIdSync(id, cloneDeep$1(newSchema), true, { doNotPushHistory: true });
15846
16784
  return;
15847
16785
  }
15848
- if (oldContent && newContent === null) {
15849
- if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { doNotPushHistory: true });
16786
+ if (oldSchema && !newSchema) {
16787
+ if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, { doNotPushHistory: true });
15850
16788
  else await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
15851
16789
  return;
15852
16790
  }
15853
- if (!oldContent || !newContent) return;
15854
- const sourceForValues = reverse ? oldContent : newContent;
16791
+ if (!oldSchema || !newSchema) return;
16792
+ const sourceForValues = reverse ? oldSchema : newSchema;
15855
16793
  if (changeRecords?.length) {
15856
16794
  const current = this.getCodeContentById(id);
15857
16795
  if (!current) return;
@@ -15920,18 +16858,6 @@
15920
16858
  "createId"
15921
16859
  ]
15922
16860
  };
15923
- /**
15924
- * 「回滚」生成的新 step 简短描述。
15925
- * 仅在 service 层使用,避免依赖 UI 层 composables。
15926
- */
15927
- var describeRevertDataSourceStep = (step) => {
15928
- const { oldSchema, newSchema, changeRecords, id } = step;
15929
- if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
15930
- if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
15931
- const title = newSchema?.title || oldSchema?.title || `${id}`;
15932
- const propPath = changeRecords?.[0]?.propPath;
15933
- return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
15934
- };
15935
16861
  var DataSource = class extends BaseService {
15936
16862
  state = (0, vue.reactive)({
15937
16863
  datasourceTypeList: [],
@@ -15942,6 +16868,12 @@
15942
16868
  events: {},
15943
16869
  methods: {}
15944
16870
  });
16871
+ /**
16872
+ * 最近一次写入历史栈的数据源历史记录 uuid。
16873
+ * 供 *AndGetHistoryId 系列方法在调用 add / update / remove 后取回本次产生的历史记录 id;
16874
+ * 普通方法不读取它,调用前由 *AndGetHistoryId 重置为 null。
16875
+ */
16876
+ lastPushedHistoryId = null;
15945
16877
  constructor() {
15946
16878
  super(canUsePluginMethods$3.sync.map((methodName) => ({
15947
16879
  name: methodName,
@@ -15991,12 +16923,12 @@
15991
16923
  id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId()
15992
16924
  };
15993
16925
  this.get("dataSources").push(newConfig);
15994
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
16926
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
15995
16927
  oldSchema: null,
15996
16928
  newSchema: newConfig,
15997
16929
  historyDescription,
15998
16930
  source: historySource
15999
- });
16931
+ })?.uuid ?? null;
16000
16932
  this.emit("add", newConfig);
16001
16933
  return newConfig;
16002
16934
  }
@@ -16014,13 +16946,13 @@
16014
16946
  const oldConfig = dataSources[index];
16015
16947
  const newConfig = cloneDeep$1(config);
16016
16948
  dataSources[index] = newConfig;
16017
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
16949
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
16018
16950
  oldSchema: oldConfig ? cloneDeep$1(oldConfig) : null,
16019
16951
  newSchema: newConfig,
16020
16952
  changeRecords,
16021
16953
  historyDescription,
16022
16954
  source: historySource
16023
- });
16955
+ })?.uuid ?? null;
16024
16956
  this.emit("update", newConfig, {
16025
16957
  oldConfig,
16026
16958
  changeRecords
@@ -16039,15 +16971,47 @@
16039
16971
  const index = dataSources.findIndex((ds) => ds.id === id);
16040
16972
  const oldConfig = index !== -1 ? dataSources[index] : null;
16041
16973
  dataSources.splice(index, 1);
16042
- if (oldConfig && !doNotPushHistory) history_default.pushDataSource(id, {
16974
+ if (oldConfig && !doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(id, {
16043
16975
  oldSchema: cloneDeep$1(oldConfig),
16044
16976
  newSchema: null,
16045
16977
  historyDescription,
16046
16978
  source: historySource
16047
- });
16979
+ })?.uuid ?? null;
16048
16980
  this.emit("remove", id);
16049
16981
  }
16050
16982
  /**
16983
+ * 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
16984
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link DataSourceStepValue.uuid}),
16985
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16986
+ *
16987
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
16988
+ */
16989
+ /** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16990
+ addAndGetHistoryId(config, options = {}) {
16991
+ this.lastPushedHistoryId = null;
16992
+ return {
16993
+ result: this.add(config, options),
16994
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16995
+ };
16996
+ }
16997
+ /** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16998
+ updateAndGetHistoryId(config, options = {}) {
16999
+ this.lastPushedHistoryId = null;
17000
+ return {
17001
+ result: this.update(config, options),
17002
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
17003
+ };
17004
+ }
17005
+ /** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
17006
+ removeAndGetHistoryId(id, options = {}) {
17007
+ this.lastPushedHistoryId = null;
17008
+ this.remove(id, options);
17009
+ return {
17010
+ result: void 0,
17011
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
17012
+ };
17013
+ }
17014
+ /**
16051
17015
  * 撤销指定数据源的最近一次变更。
16052
17016
  *
16053
17017
  * 内部走 add / update / remove,因此会自动触发 dataSourceService 的事件,
@@ -16115,10 +17079,25 @@
16115
17079
  revert(id, index) {
16116
17080
  const entry = history_default.getDataSourceStepList(id)[index];
16117
17081
  if (!entry?.applied) return null;
16118
- if (entry.step.oldSchema && entry.step.newSchema && !entry.step.changeRecords?.length) return null;
16119
- const description = `回滚 #${index + 1}: ${describeRevertDataSourceStep(entry.step)}`;
17082
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
17083
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
17084
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.title)}`;
16120
17085
  return this.applyRevertStep(entry.step, description);
16121
17086
  }
17087
+ /**
17088
+ * 通过历史记录 uuid 回滚数据源历史步骤,语义同 {@link revert},
17089
+ * 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
17090
+ * 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
17091
+ *
17092
+ * @param uuids 目标历史记录的 uuid 列表,通常由 {@link addAndGetHistoryId} 等方法返回的 `historyIds`
17093
+ */
17094
+ revertById(uuids) {
17095
+ return uuids.map((uuid) => {
17096
+ const location = history_default.findDataSourceStepLocationByUuid(uuid);
17097
+ if (!location) return null;
17098
+ return this.revert(location.id, location.index);
17099
+ });
17100
+ }
16122
17101
  createId() {
16123
17102
  return `ds_${(0, _tmagic_utils.guid)()}`;
16124
17103
  }
@@ -16177,15 +17156,16 @@
16177
17156
  * 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
16178
17157
  */
16179
17158
  applyRevertStep(step, historyDescription) {
16180
- const { id, oldSchema, newSchema, changeRecords } = step;
16181
- if (oldSchema === null && newSchema) {
17159
+ const { id } = step;
17160
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
17161
+ if (!oldSchema && newSchema) {
16182
17162
  this.remove(`${id}`, {
16183
17163
  historyDescription,
16184
17164
  historySource: "rollback"
16185
17165
  });
16186
17166
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
16187
17167
  }
16188
- if (oldSchema && newSchema === null) {
17168
+ if (oldSchema && !newSchema) {
16189
17169
  this.add(cloneDeep$1(oldSchema), {
16190
17170
  historyDescription,
16191
17171
  historySource: "rollback"
@@ -16234,13 +17214,14 @@
16234
17214
  * @param reverse true=撤销,false=重做
16235
17215
  */
16236
17216
  applyHistoryStep(step, reverse) {
16237
- const { id, oldSchema, newSchema, changeRecords } = step;
16238
- if (oldSchema === null && newSchema) {
17217
+ const { id } = step;
17218
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
17219
+ if (!oldSchema && newSchema) {
16239
17220
  if (reverse) this.remove(`${id}`, { doNotPushHistory: true });
16240
17221
  else this.add(cloneDeep$1(newSchema), { doNotPushHistory: true });
16241
17222
  return;
16242
17223
  }
16243
- if (oldSchema && newSchema === null) {
17224
+ if (oldSchema && !newSchema) {
16244
17225
  if (reverse) this.add(cloneDeep$1(oldSchema), { doNotPushHistory: true });
16245
17226
  else this.remove(`${id}`, { doNotPushHistory: true });
16246
17227
  return;
@@ -16891,7 +17872,7 @@
16891
17872
  //#region packages/editor/src/initService.ts
16892
17873
  var initServiceState = (props, { editorService, historyService, componentListService, propsService, eventsService, uiService, codeBlockService, keybindingService, dataSourceService, depService }) => {
16893
17874
  (0, vue.watch)(() => props.modelValue, (modelValue) => {
16894
- editorService.set("root", modelValue || null);
17875
+ editorService.set("root", modelValue || null, { historySource: "initial" });
16895
17876
  }, { immediate: true });
16896
17877
  (0, vue.watch)(() => props.disabledMultiSelect, (disabledMultiSelect) => {
16897
17878
  editorService.set("disabledMultiSelect", disabledMultiSelect || false);
@@ -17755,7 +18736,8 @@
17755
18736
  */
17756
18737
  const isCompareMode = (0, vue.computed)(() => Boolean(props.isCompare && props.lastValues));
17757
18738
  const notEditable = (0, vue.computed)(() => (0, _tmagic_form.filterFunction)(mForm, props.config.notEditable, props));
17758
- const hasCodeBlockSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.CODE_BLOCK));
18739
+ const codeBlockSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.CODE_BLOCK));
18740
+ const hasCodeBlockSidePanel = (0, vue.computed)(() => codeBlockSidePanel.value);
17759
18741
  /**
17760
18742
  * 根据代码块id获取代码块参数配置
17761
18743
  * @param codeId 代码块ID
@@ -17810,6 +18792,8 @@
17810
18792
  emit("change", props.model[props.name], eventData);
17811
18793
  };
17812
18794
  const editCode = (id) => {
18795
+ const sideBarItem = codeBlockSidePanel.value;
18796
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.CODE_BLOCK);
17813
18797
  eventBus?.emit("edit-code", id);
17814
18798
  };
17815
18799
  return (_ctx, _cache) => {
@@ -17942,6 +18926,15 @@
17942
18926
  calcBoxPosition();
17943
18927
  addDialogVisible.value = true;
17944
18928
  };
18929
+ const editField = (row, index) => {
18930
+ fieldValues.value = {
18931
+ ...row,
18932
+ index
18933
+ };
18934
+ fieldTitle.value = `编辑${row.title}`;
18935
+ calcBoxPosition();
18936
+ addDialogVisible.value = true;
18937
+ };
17945
18938
  const fieldChange = ({ index, ...value }, data) => {
17946
18939
  addDialogVisible.value = false;
17947
18940
  if (index > -1) emit("change", value, {
@@ -17993,13 +18986,7 @@
17993
18986
  actions: [{
17994
18987
  text: "编辑",
17995
18988
  handler: (row, index) => {
17996
- fieldValues.value = {
17997
- ...row,
17998
- index
17999
- };
18000
- fieldTitle.value = `编辑${row.title}`;
18001
- calcBoxPosition();
18002
- addDialogVisible.value = true;
18989
+ editField(row, index);
18003
18990
  }
18004
18991
  }, {
18005
18992
  text: "删除",
@@ -18186,6 +19173,20 @@
18186
19173
  const addFromJsonDialogVisible = (0, vue.useModel)(__props, "visible1");
18187
19174
  const { height: editorHeight } = useEditorContentHeight();
18188
19175
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, (0, vue.inject)("parentFloating", (0, vue.ref)(null)));
19176
+ /**
19177
+ * 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
19178
+ * 当前层消费 path[0],并把剩余路径下发给嵌套字段,实现逐层打开。
19179
+ */
19180
+ const editingFieldPath = (0, vue.inject)("editingDataSourceFieldPath", (0, vue.computed)(() => []));
19181
+ (0, vue.provide)("editingDataSourceFieldPath", (0, vue.computed)(() => editingFieldPath.value.slice(1)));
19182
+ (0, vue.onMounted)(() => {
19183
+ const path = editingFieldPath.value;
19184
+ if (!path.length) return;
19185
+ const fields = props.model[props.name] || [];
19186
+ const index = fields.findIndex((field) => field.name === path[0]);
19187
+ if (index === -1) return;
19188
+ editField(fields[index], index);
19189
+ });
18189
19190
  return (_ctx, _cache) => {
18190
19191
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$36, [
18191
19192
  (0, vue.createVNode)((0, vue.unref)(_tmagic_table.MagicTable), {
@@ -18567,6 +19568,19 @@
18567
19568
  const codeConfig = (0, vue.ref)();
18568
19569
  const codeBlockEditorRef = (0, vue.useTemplateRef)("codeBlockEditor");
18569
19570
  let editIndex = -1;
19571
+ const editMethod = (method, index) => {
19572
+ let codeContent = "({ params, dataSource, app }) => {\n // place your code here\n}";
19573
+ if (method.content) if (typeof method.content !== "string") codeContent = method.content.toString();
19574
+ else codeContent = method.content;
19575
+ codeConfig.value = {
19576
+ ...cloneDeep$1(method),
19577
+ content: codeContent
19578
+ };
19579
+ editIndex = index;
19580
+ (0, vue.nextTick)(() => {
19581
+ codeBlockEditorRef.value?.show();
19582
+ });
19583
+ };
18570
19584
  const methodColumns = [
18571
19585
  {
18572
19586
  label: "名称",
@@ -18591,17 +19605,7 @@
18591
19605
  actions: [{
18592
19606
  text: "编辑",
18593
19607
  handler: (method, index) => {
18594
- let codeContent = "({ params, dataSource, app }) => {\n // place your code here\n}";
18595
- if (method.content) if (typeof method.content !== "string") codeContent = method.content.toString();
18596
- else codeContent = method.content;
18597
- codeConfig.value = {
18598
- ...cloneDeep$1(method),
18599
- content: codeContent
18600
- };
18601
- editIndex = index;
18602
- (0, vue.nextTick)(() => {
18603
- codeBlockEditorRef.value?.show();
18604
- });
19608
+ editMethod(method, index);
18605
19609
  }
18606
19610
  }, {
18607
19611
  text: "删除",
@@ -18649,6 +19653,16 @@
18649
19653
  codeConfig.value = void 0;
18650
19654
  codeBlockEditorRef.value?.hide();
18651
19655
  };
19656
+ /** 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的方法名 */
19657
+ const editingMethodName = (0, vue.inject)("editingDataSourceMethodName", (0, vue.computed)(() => ""));
19658
+ (0, vue.onMounted)(() => {
19659
+ const methodName = editingMethodName.value;
19660
+ if (!methodName) return;
19661
+ const methods = props.model[props.name] || [];
19662
+ const index = methods.findIndex((method) => method.name === methodName);
19663
+ if (index === -1) return;
19664
+ editMethod(methods[index], index);
19665
+ });
18652
19666
  return (_ctx, _cache) => {
18653
19667
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$34, [
18654
19668
  (0, vue.createVNode)((0, vue.unref)(_tmagic_table.MagicTable), {
@@ -19076,7 +20090,7 @@
19076
20090
  const eventBus = (0, vue.inject)("eventBus");
19077
20091
  const emit = __emit;
19078
20092
  const props = __props;
19079
- const hasDataSourceSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.DATA_SOURCE));
20093
+ const dataSourceSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.DATA_SOURCE));
19080
20094
  const notEditable = (0, vue.computed)(() => (0, _tmagic_form.filterFunction)(mForm, props.config.notEditable, props));
19081
20095
  /** 对比模式下隐藏查看/编辑操作按钮,仅保留只读展示。 */
19082
20096
  const isCompare = (0, vue.computed)(() => Boolean(mForm?.isCompare));
@@ -19164,9 +20178,11 @@
19164
20178
  emit("change", props.model[props.name], eventData);
19165
20179
  };
19166
20180
  const editCodeHandler = () => {
19167
- const [id] = props.model[props.name];
20181
+ const [id, methodName] = props.model[props.name];
19168
20182
  if (!dataSourceService.getDataSourceById(id)) return;
19169
- eventBus?.emit("edit-data-source", id);
20183
+ const sideBarItem = dataSourceSidePanel.value;
20184
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
20185
+ eventBus?.emit("edit-data-source-method", id, methodName);
19170
20186
  };
19171
20187
  return (_ctx, _cache) => {
19172
20188
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$31, [(0, vue.createElementVNode)("div", _hoisted_2$6, [(0, vue.createVNode)((0, vue.unref)(_tmagic_form.MCascader), {
@@ -19185,7 +20201,7 @@
19185
20201
  "size",
19186
20202
  "disabled",
19187
20203
  "prop"
19188
- ]), __props.model[__props.name] && isCustomMethod.value && hasDataSourceSidePanel.value && !isCompare.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
20204
+ ]), __props.model[__props.name] && isCustomMethod.value && dataSourceSidePanel.value && !isCompare.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
19189
20205
  key: 0,
19190
20206
  content: notEditable.value ? "查看" : "编辑"
19191
20207
  }, {
@@ -19301,9 +20317,15 @@
19301
20317
  modelValue.value = v;
19302
20318
  emit("change", v);
19303
20319
  };
19304
- const hasDataSourceSidePanel = (0, vue.computed)(() => uiService.get("sideBarItems").find((item) => item.$key === SideItemKey.DATA_SOURCE));
20320
+ const dataSourceSidePanel = (0, vue.computed)(() => uiService.get("sideBarItems").find((item) => item.$key === SideItemKey.DATA_SOURCE));
20321
+ const hasDataSourceSidePanel = (0, vue.computed)(() => dataSourceSidePanel.value);
19305
20322
  const editHandler = (id) => {
19306
- eventBus?.emit("edit-data-source", (0, _tmagic_utils.removeDataSourceFieldPrefix)(id));
20323
+ const sideBarItem = dataSourceSidePanel.value;
20324
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
20325
+ const dataSourceId = (0, _tmagic_utils.removeDataSourceFieldPrefix)(id);
20326
+ const fieldPath = selectFieldsId.value;
20327
+ if (fieldPath.length) eventBus?.emit("edit-data-source-field", dataSourceId, [...fieldPath]);
20328
+ else eventBus?.emit("edit-data-source", dataSourceId);
19307
20329
  };
19308
20330
  return (_ctx, _cache) => {
19309
20331
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$30, [__props.dataSourceId ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicCascader), {
@@ -21522,6 +22544,13 @@
21522
22544
  ] }));
21523
22545
  const selectDirection = (d) => direction.value = d || "";
21524
22546
  const emit = __emit;
22547
+ const props = __props;
22548
+ const hasValue = (value) => value !== void 0 && value !== null && value !== "";
22549
+ const isConfigured = (d) => [
22550
+ "Width",
22551
+ "Color",
22552
+ "Style"
22553
+ ].some((key) => hasValue(props.model?.[`border${d}${key}`]));
21525
22554
  const change = (value, eventData) => {
21526
22555
  eventData.changeRecords?.forEach((record) => {
21527
22556
  emit("change", record.value, { modifyKey: record.propPath });
@@ -21531,25 +22560,40 @@
21531
22560
  return (_ctx, _cache) => {
21532
22561
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$1, [(0, vue.createElementVNode)("div", _hoisted_2, [
21533
22562
  (0, vue.createElementVNode)("div", _hoisted_3, [(0, vue.createElementVNode)("div", {
21534
- class: (0, vue.normalizeClass)(["border-icon border-icon-top", { active: direction.value === "Top" }]),
22563
+ class: (0, vue.normalizeClass)(["border-icon border-icon-top", {
22564
+ active: direction.value === "Top",
22565
+ configured: isConfigured("Top")
22566
+ }]),
21535
22567
  onClick: _cache[0] || (_cache[0] = ($event) => selectDirection("Top"))
21536
22568
  }, null, 2)]),
21537
22569
  (0, vue.createElementVNode)("div", _hoisted_4, [
21538
22570
  (0, vue.createElementVNode)("div", {
21539
- class: (0, vue.normalizeClass)(["border-icon border-icon-left", { active: direction.value === "Left" }]),
22571
+ class: (0, vue.normalizeClass)(["border-icon border-icon-left", {
22572
+ active: direction.value === "Left",
22573
+ configured: isConfigured("Left")
22574
+ }]),
21540
22575
  onClick: _cache[1] || (_cache[1] = ($event) => selectDirection("Left"))
21541
22576
  }, null, 2),
21542
22577
  (0, vue.createElementVNode)("div", {
21543
- class: (0, vue.normalizeClass)(["border-icon", { active: direction.value === "" }]),
22578
+ class: (0, vue.normalizeClass)(["border-icon", {
22579
+ active: direction.value === "",
22580
+ configured: isConfigured("")
22581
+ }]),
21544
22582
  onClick: _cache[2] || (_cache[2] = ($event) => selectDirection())
21545
22583
  }, null, 2),
21546
22584
  (0, vue.createElementVNode)("div", {
21547
- class: (0, vue.normalizeClass)(["border-icon border-icon-right", { active: direction.value === "Right" }]),
22585
+ class: (0, vue.normalizeClass)(["border-icon border-icon-right", {
22586
+ active: direction.value === "Right",
22587
+ configured: isConfigured("Right")
22588
+ }]),
21548
22589
  onClick: _cache[3] || (_cache[3] = ($event) => selectDirection("Right"))
21549
22590
  }, null, 2)
21550
22591
  ]),
21551
22592
  (0, vue.createElementVNode)("div", _hoisted_5, [(0, vue.createElementVNode)("div", {
21552
- class: (0, vue.normalizeClass)(["border-icon border-icon-bottom", { active: direction.value === "Bottom" }]),
22593
+ class: (0, vue.normalizeClass)(["border-icon border-icon-bottom", {
22594
+ active: direction.value === "Bottom",
22595
+ configured: isConfigured("Bottom")
22596
+ }]),
21553
22597
  onClick: _cache[4] || (_cache[4] = ($event) => selectDirection("Bottom"))
21554
22598
  }, null, 2)])
21555
22599
  ]), (0, vue.createElementVNode)("div", _hoisted_6, [(0, vue.createVNode)((0, vue.unref)(_tmagic_form.MContainer), {
@@ -22166,6 +23210,7 @@
22166
23210
  exports.H_GUIDE_LINE_STORAGE_KEY = H_GUIDE_LINE_STORAGE_KEY;
22167
23211
  exports.HistoryDiffDialog = HistoryDiffDialog_default;
22168
23212
  exports.HistoryListBucket = Bucket_default;
23213
+ exports.HistoryListBucketTab = BucketTab_default;
22169
23214
  exports.Icon = Icon_default;
22170
23215
  exports.IdleTask = IdleTask;
22171
23216
  exports.KeyBindingCommand = KeyBindingCommand;
@@ -22215,17 +23260,24 @@
22215
23260
  exports.classifyDragSources = classifyDragSources;
22216
23261
  exports.codeBlockService = codeBlock_default;
22217
23262
  exports.collectRelatedNodes = collectRelatedNodes;
23263
+ exports.createStackStep = createStackStep;
22218
23264
  exports.dataSourceService = dataSource_default;
22219
23265
  exports.debug = debug;
22220
23266
  exports.default = plugin_default;
22221
23267
  exports.defaultIsExpandable = defaultIsExpandable;
22222
23268
  exports.depService = dep_default;
23269
+ exports.describeRevertStep = describeRevertStep;
23270
+ exports.describeStepForRevert = describeStepForRevert;
23271
+ exports.deserializeStacks = deserializeStacks;
22223
23272
  Object.defineProperty(exports, "designPlugin", {
22224
23273
  enumerable: true,
22225
23274
  get: function() {
22226
23275
  return _tmagic_design.default;
22227
23276
  }
22228
23277
  });
23278
+ exports.detectPageTargetId = detectPageTargetId;
23279
+ exports.detectPageTargetName = detectPageTargetName;
23280
+ exports.detectStackOpType = detectStackOpType;
22229
23281
  exports.displayTabConfig = displayTabConfig;
22230
23282
  exports.editorNodeMergeCustomizer = editorNodeMergeCustomizer;
22231
23283
  exports.editorService = editor_default;
@@ -22255,22 +23307,33 @@
22255
23307
  exports.getFormValue = getFormValue;
22256
23308
  exports.getGuideLineFromCache = getGuideLineFromCache;
22257
23309
  exports.getInitPositionStyle = getInitPositionStyle;
23310
+ exports.getLastPushedHistoryIds = getLastPushedHistoryIds;
22258
23311
  exports.getNodeIndex = getNodeIndex;
23312
+ exports.getOrCreateStack = getOrCreateStack;
22259
23313
  exports.getPageFragmentList = getPageFragmentList;
22260
23314
  exports.getPageList = getPageList;
22261
23315
  exports.getPageNameList = getPageNameList;
22262
23316
  exports.getPositionInContainer = getPositionInContainer;
22263
23317
  exports.getRelativeStyle = getRelativeStyle;
22264
23318
  exports.historyService = history_default;
23319
+ exports.idbDelete = idbDelete;
23320
+ exports.idbGet = idbGet;
23321
+ exports.idbSet = idbSet;
22265
23322
  exports.info = info;
22266
23323
  exports.isIncludeDataSource = isIncludeDataSource;
23324
+ exports.isIndexedDBSupported = isIndexedDBSupported;
22267
23325
  exports.loadMonaco = monaco_editor_default;
22268
23326
  exports.log = log;
23327
+ exports.markStackSaved = markStackSaved;
23328
+ exports.mergePageSteps = mergePageSteps;
23329
+ exports.mergeStackSteps = mergeStackSteps;
22269
23330
  exports.moveItemsInContainer = moveItemsInContainer;
22270
23331
  exports.numberOptions = numberOptions;
23332
+ exports.openIndexedDB = openIndexedDB;
22271
23333
  exports.propsService = props_default;
22272
23334
  exports.resolveSelectedNode = resolveSelectedNode;
22273
23335
  exports.serializeConfig = serializeConfig;
23336
+ exports.serializeStacks = serializeStacks;
22274
23337
  exports.setChildrenLayout = setChildrenLayout;
22275
23338
  exports.setEditorConfig = setEditorConfig;
22276
23339
  exports.setLayout = setLayout;
@@ -22285,6 +23348,7 @@
22285
23348
  });
22286
23349
  exports.toggleFixedPosition = toggleFixedPosition;
22287
23350
  exports.uiService = ui_default;
23351
+ exports.undoFloor = undoFloor;
22288
23352
  exports.updateStatus = updateStatus;
22289
23353
  exports.useCodeBlockEdit = useCodeBlockEdit;
22290
23354
  exports.useEditorContentHeight = useEditorContentHeight;