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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +15 -1
  2. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
  3. package/dist/es/hooks/use-code-block-edit.js +6 -3
  4. package/dist/es/hooks/use-data-source-edit.js +5 -2
  5. package/dist/es/hooks/use-stage.js +6 -3
  6. package/dist/es/index.js +6 -3
  7. package/dist/es/initService.js +1 -1
  8. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -51
  11. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -36
  12. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +152 -93
  13. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +36 -10
  14. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +334 -208
  15. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
  16. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -57
  17. package/dist/es/layouts/history-list/composables.js +118 -25
  18. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
  19. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +5 -1
  20. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
  22. package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +3 -3
  23. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
  25. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  26. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
  27. package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +5 -5
  28. package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
  29. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +1 -1
  30. package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
  31. package/dist/es/services/codeBlock.js +105 -42
  32. package/dist/es/services/dataSource.js +85 -35
  33. package/dist/es/services/editor.js +317 -122
  34. package/dist/es/services/history.js +270 -204
  35. package/dist/es/services/keybinding.js +3 -3
  36. package/dist/es/services/ui.js +3 -0
  37. package/dist/es/style.css +65 -10
  38. package/dist/es/utils/content-menu.js +17 -9
  39. package/dist/es/utils/editor.js +35 -1
  40. package/dist/es/utils/history.js +223 -0
  41. package/dist/es/utils/indexed-db.js +86 -0
  42. package/dist/es/utils/undo-redo.js +60 -1
  43. package/dist/style.css +65 -10
  44. package/dist/tmagic-editor.umd.cjs +2047 -954
  45. package/package.json +7 -7
  46. package/src/components/CompareForm.vue +21 -1
  47. package/src/components/ToolButton.vue +2 -2
  48. package/src/hooks/use-code-block-edit.ts +4 -3
  49. package/src/hooks/use-data-source-edit.ts +2 -2
  50. package/src/hooks/use-stage.ts +3 -3
  51. package/src/index.ts +1 -0
  52. package/src/initService.ts +1 -1
  53. package/src/layouts/Framework.vue +1 -1
  54. package/src/layouts/NavMenu.vue +1 -1
  55. package/src/layouts/history-list/Bucket.vue +34 -67
  56. package/src/layouts/history-list/BucketTab.vue +46 -51
  57. package/src/layouts/history-list/GroupRow.vue +150 -98
  58. package/src/layouts/history-list/HistoryDiffDialog.vue +96 -69
  59. package/src/layouts/history-list/HistoryListPanel.vue +298 -111
  60. package/src/layouts/history-list/InitialRow.vue +25 -9
  61. package/src/layouts/history-list/PageTab.vue +58 -48
  62. package/src/layouts/history-list/composables.ts +210 -29
  63. package/src/layouts/page-bar/PageBar.vue +4 -2
  64. package/src/layouts/props-panel/PropsPanel.vue +5 -1
  65. package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
  66. package/src/layouts/sidebar/Sidebar.vue +6 -1
  67. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
  68. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
  69. package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
  70. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  71. package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
  72. package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
  73. package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
  74. package/src/layouts/workspace/viewer/Stage.vue +1 -1
  75. package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
  76. package/src/services/codeBlock.ts +129 -35
  77. package/src/services/dataSource.ts +105 -34
  78. package/src/services/editor.ts +470 -157
  79. package/src/services/history.ts +312 -203
  80. package/src/services/keybinding.ts +3 -3
  81. package/src/services/ui.ts +7 -0
  82. package/src/theme/history-list-panel.scss +87 -6
  83. package/src/theme/page-bar.scss +0 -4
  84. package/src/theme/props-panel.scss +3 -3
  85. package/src/type.ts +221 -61
  86. package/src/utils/content-menu.ts +18 -9
  87. package/src/utils/editor.ts +41 -1
  88. package/src/utils/history.ts +298 -0
  89. package/src/utils/index.ts +2 -0
  90. package/src/utils/indexed-db.ts +122 -0
  91. package/src/utils/undo-redo.ts +88 -0
  92. package/types/index.d.ts +871 -308
@@ -1,6 +1,13 @@
1
- import { createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, withModifiers } from "vue";
1
+ import { formatHistoryFullTime, formatHistoryTime } from "./composables.js";
2
+ import { computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, toDisplayString, withModifiers } from "vue";
2
3
  //#region packages/editor/src/layouts/history-list/InitialRow.vue?vue&type=script&setup=true&lang.ts
3
4
  var _hoisted_1 = ["title"];
5
+ var _hoisted_2 = { class: "m-editor-history-list-item-desc" };
6
+ var _hoisted_3 = {
7
+ key: 0,
8
+ class: "m-editor-history-list-item-actions"
9
+ };
10
+ var _hoisted_4 = ["title"];
4
11
  var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
5
12
  name: "MEditorHistoryListInitialRow",
6
13
  __name: "InitialRow",
@@ -9,7 +16,8 @@ var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
9
16
  gotoEnabled: {
10
17
  type: Boolean,
11
18
  default: true
12
- }
19
+ },
20
+ marker: { default: void 0 }
13
21
  },
14
22
  emits: ["goto-initial"],
15
23
  setup(__props, { emit: __emit }) {
@@ -17,10 +25,19 @@ var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
17
25
  * 「初始状态」记录行:渲染于历史列表底部,作为整个栈的"零点"。
18
26
  * - 点击该行会把对应栈撤销到 cursor === 0(即没有任何已应用步骤),等同于回到所有修改之前。
19
27
  * - 当对应栈本身已处于 cursor === 0 时(isCurrent=true),用户已在初始状态,点击不再触发动作。
28
+ * - 当上层传入 `marker`(设置 root 时为该页生成的「未修改的初始状态」标记)时,
29
+ * 用标记的文案与时间渲染本行;标记不进入撤销/重做栈,仅作为该页基线展示。
20
30
  *
21
31
  * 该行不是真实 step,仅作为 UI 入口;上层负责把"点击"翻译为 `service.goto*(0)`。
22
32
  */
23
33
  const props = __props;
34
+ const desc = computed(() => props.marker?.historyDescription || "未修改的初始状态");
35
+ const time = computed(() => formatHistoryTime(props.marker?.timestamp));
36
+ const timeTitle = computed(() => formatHistoryFullTime(props.marker?.timestamp));
37
+ const rowTitle = computed(() => {
38
+ const base = props.marker?.historyDescription || "未修改的初始状态";
39
+ return props.isCurrent ? `当前已回到${base}` : `点击回到${base}`;
40
+ });
24
41
  const emit = __emit;
25
42
  const onClick = () => {
26
43
  if (props.isCurrent) return;
@@ -32,20 +49,24 @@ var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
32
49
  "is-current": __props.isCurrent,
33
50
  "is-clickable": !__props.isCurrent
34
51
  }]),
35
- title: __props.isCurrent ? "当前已回到未修改的初始状态" : "点击回到未修改的初始状态"
52
+ title: rowTitle.value
36
53
  }, [
37
54
  _cache[0] || (_cache[0] = createElementVNode("span", {
38
55
  class: "m-editor-history-list-item-index",
39
56
  title: "历史步骤编号 #0(未修改的初始状态)"
40
57
  }, "#0", -1)),
41
58
  _cache[1] || (_cache[1] = createElementVNode("span", { class: "m-editor-history-list-item-op op-initial" }, "初始", -1)),
42
- _cache[2] || (_cache[2] = createElementVNode("span", { class: "m-editor-history-list-item-desc" }, "未修改的初始状态", -1)),
43
- __props.gotoEnabled && !__props.isCurrent ? (openBlock(), createElementBlock("span", {
44
- key: 0,
59
+ createElementVNode("span", _hoisted_2, toDisplayString(desc.value), 1),
60
+ __props.gotoEnabled && !__props.isCurrent ? (openBlock(), createElementBlock("span", _hoisted_3, [createElementVNode("span", {
45
61
  class: "m-editor-history-list-item-goto",
46
62
  title: "回到该记录",
47
63
  onClick: withModifiers(onClick, ["stop"])
48
- }, "回到")) : createCommentVNode("v-if", true)
64
+ }, "回到")])) : createCommentVNode("v-if", true),
65
+ time.value ? (openBlock(), createElementBlock("span", {
66
+ key: 1,
67
+ class: "m-editor-history-list-item-time",
68
+ title: timeTitle.value
69
+ }, toDisplayString(time.value), 9, _hoisted_4)) : createCommentVNode("v-if", true)
49
70
  ], 10, _hoisted_1);
50
71
  };
51
72
  }
@@ -1,4 +1,4 @@
1
- import { describePageGroup, describePageStep, formatHistoryFullTime, formatHistoryTime, groupTimestamp } from "./composables.js";
1
+ import { describePageGroup, describePageStep, isHistoryGroupExpanded, isPageStepRevertable, toRowGroup } from "./composables.js";
2
2
  import GroupRow_default from "./GroupRow.js";
3
3
  import InitialRow_default from "./InitialRow.js";
4
4
  import { TMagicScrollbar } from "@tmagic/design";
@@ -8,96 +8,86 @@ var _hoisted_1 = {
8
8
  key: 0,
9
9
  class: "m-editor-history-list-empty"
10
10
  };
11
- var _hoisted_2 = { class: "m-editor-history-list-ul" };
11
+ var _hoisted_2 = {
12
+ key: 0,
13
+ class: "m-editor-history-list-toolbar"
14
+ };
15
+ var _hoisted_3 = { class: "m-editor-history-list-ul" };
12
16
  var PageTab_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
13
17
  name: "MEditorHistoryListPageTab",
14
18
  __name: "PageTab",
15
19
  props: {
16
20
  list: {},
17
- expanded: {}
21
+ expanded: {},
22
+ marker: {}
18
23
  },
19
24
  emits: [
20
25
  "toggle",
21
26
  "goto",
22
27
  "goto-initial",
23
28
  "diff-step",
24
- "revert-step"
29
+ "revert-step",
30
+ "select",
31
+ "clear"
25
32
  ],
26
33
  setup(__props) {
27
34
  const props = __props;
28
35
  /**
29
36
  * 当前 step 是否可查看差异:
30
37
  * - 仅 update 操作;
31
- * - 单节点更新(updatedItems.length === 1),且 oldNode / newNode 都存在。
38
+ * - 单节点更新(diff.length === 1),且 oldSchema / newSchema 都存在。
32
39
  * 多节点更新难以选定单一对比目标,统一不展示差异入口。
33
40
  */
34
41
  const isPageStepDiffable = (step) => {
35
42
  if (step.opType !== "update") return false;
36
- const items = step.updatedItems ?? [];
43
+ const items = step.diff ?? [];
37
44
  if (items.length !== 1) return false;
38
- return Boolean(items[0]?.oldNode && items[0]?.newNode);
45
+ return Boolean(items[0]?.oldSchema && items[0]?.newSchema);
46
+ };
47
+ /** 页面历史的描述 / 可操作性判定集合,注入给统一的 `toRowGroup`。 */
48
+ const descriptor = {
49
+ describeGroup: describePageGroup,
50
+ describeStep: describePageStep,
51
+ isStepDiffable: isPageStepDiffable,
52
+ isStepRevertable: isPageStepRevertable
39
53
  };
54
+ const rowKey = (group) => `pg-${group.steps[0]?.index}`;
55
+ const toRow = (group) => toRowGroup(group, rowKey(group), descriptor);
40
56
  /**
41
57
  * 是否处于"初始状态"——即对应页面历史栈 cursor===0:
42
- * 当 list 中所有 group 的 applied 都为 false 时即为该状态。
43
- * 没有任何 group 的情况由外层"暂无操作记录"分支兜底,本计算可以不考虑。
58
+ * 当 list 中所有 group 的 applied 都为 false 时即为该状态(空列表 `every` 返回 true,
59
+ * 即仅有 marker、无任何操作记录时也视为处于初始状态)。
44
60
  */
45
- const isInitial = computed(() => props.list.length > 0 && props.list.every((g) => !g.applied));
61
+ const isInitial = computed(() => props.list.every((g) => !g.applied));
46
62
  return (_ctx, _cache) => {
47
- return !__props.list.length ? (openBlock(), createElementBlock("div", _hoisted_1, "暂无操作记录")) : (openBlock(), createBlock(unref(TMagicScrollbar), {
48
- key: 1,
49
- "max-height": "360px"
50
- }, {
51
- default: withCtx(() => [createElementVNode("ul", _hoisted_2, [
63
+ return !__props.list.length && !__props.marker ? (openBlock(), createElementBlock("div", _hoisted_1, "暂无操作记录")) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [__props.list.length ? (openBlock(), createElementBlock("div", _hoisted_2, [createElementVNode("span", {
64
+ class: "m-editor-history-list-clear",
65
+ title: "清空当前页面的历史记录",
66
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
67
+ }, "清空")])) : createCommentVNode("v-if", true), createVNode(unref(TMagicScrollbar), { "max-height": "360px" }, {
68
+ default: withCtx(() => [createElementVNode("ul", _hoisted_3, [
52
69
  (openBlock(true), createElementBlock(Fragment, null, renderList(__props.list, (group) => {
53
70
  return openBlock(), createBlock(GroupRow_default, {
54
- key: `pg-${group.steps[0]?.index}`,
55
- "group-key": `pg-${group.steps[0]?.index}`,
56
- applied: group.applied,
57
- merged: group.steps.length > 1,
58
- "op-type": group.opType,
59
- desc: unref(describePageGroup)(group),
60
- time: unref(formatHistoryTime)(unref(groupTimestamp)(group)),
61
- "time-title": unref(formatHistoryFullTime)(unref(groupTimestamp)(group)),
62
- "step-count": group.steps.length,
63
- "sub-steps": group.steps.map((s) => ({
64
- index: s.index,
65
- applied: s.applied,
66
- isCurrent: s.isCurrent,
67
- desc: unref(describePageStep)(s.step),
68
- diffable: isPageStepDiffable(s.step),
69
- revertable: s.applied,
70
- time: unref(formatHistoryTime)(s.step.timestamp),
71
- timeTitle: unref(formatHistoryFullTime)(s.step.timestamp)
72
- })),
73
- "is-current": group.isCurrent,
74
- expanded: !!__props.expanded[`pg-${group.steps[0]?.index}`],
75
- onToggle: _cache[0] || (_cache[0] = (key) => _ctx.$emit("toggle", key)),
76
- onGoto: _cache[1] || (_cache[1] = (index) => _ctx.$emit("goto", index)),
77
- onDiffStep: _cache[2] || (_cache[2] = (index) => _ctx.$emit("diff-step", index)),
78
- onRevertStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("revert-step", index))
79
- }, null, 8, [
80
- "group-key",
81
- "applied",
82
- "merged",
83
- "op-type",
84
- "desc",
85
- "time",
86
- "time-title",
87
- "step-count",
88
- "sub-steps",
89
- "is-current",
90
- "expanded"
91
- ]);
71
+ key: rowKey(group),
72
+ group: toRow(group),
73
+ expanded: unref(isHistoryGroupExpanded)(__props.expanded, rowKey(group)),
74
+ "select-enabled": true,
75
+ onToggle: _cache[1] || (_cache[1] = (key) => _ctx.$emit("toggle", key)),
76
+ onGoto: _cache[2] || (_cache[2] = (index) => _ctx.$emit("goto", index)),
77
+ onDiffStep: _cache[3] || (_cache[3] = (index) => _ctx.$emit("diff-step", index)),
78
+ onRevertStep: _cache[4] || (_cache[4] = (index) => _ctx.$emit("revert-step", index)),
79
+ onSelect: _cache[5] || (_cache[5] = (index) => _ctx.$emit("select", index))
80
+ }, null, 8, ["group", "expanded"]);
92
81
  }), 128)),
93
- createCommentVNode("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n "),
82
+ createCommentVNode("\n 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),\n 作为\"未修改\"零点。当所有 group 都未 applied 时它即为当前位置。\n 设置 root 时生成的「未修改的初始状态」标记(marker)会作为该行的文案与时间来源。\n "),
94
83
  createVNode(InitialRow_default, {
95
84
  "is-current": isInitial.value,
96
- onGotoInitial: _cache[4] || (_cache[4] = ($event) => _ctx.$emit("goto-initial"))
97
- }, null, 8, ["is-current"])
85
+ marker: __props.marker,
86
+ onGotoInitial: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("goto-initial"))
87
+ }, null, 8, ["is-current", "marker"])
98
88
  ])]),
99
89
  _: 1
100
- }));
90
+ })], 64));
101
91
  };
102
92
  }
103
93
  });
@@ -2,6 +2,8 @@ import { useServices } from "../../hooks/use-services.js";
2
2
  import { datetimeFormatter } from "@tmagic/form";
3
3
  import { computed, reactive } from "vue";
4
4
  //#region packages/editor/src/layouts/history-list/composables.ts
5
+ /** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
6
+ var isHistoryGroupExpanded = (expanded, key) => expanded[key] !== false;
5
7
  /**
6
8
  * 历史记录面板共享逻辑:
7
9
  * - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
@@ -15,10 +17,11 @@ var useHistoryList = () => {
15
17
  /**
16
18
  * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
17
19
  * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
20
+ * 合并组默认展开;仅当值为 `false` 时表示收起。
18
21
  */
19
22
  const expanded = reactive({});
20
23
  const toggleGroup = (key) => {
21
- expanded[key] = !expanded[key];
24
+ expanded[key] = expanded[key] === false;
22
25
  };
23
26
  const pageGroups = computed(() => historyService.getPageHistoryGroups());
24
27
  const dataSourceGroups = computed(() => historyService.getDataSourceHistoryGroups());
@@ -73,6 +76,62 @@ var opLabel = (op) => {
73
76
  default: return "修改";
74
77
  }
75
78
  };
79
+ /** 内置操作途径的中文文案;自定义来源直接回显原值,未知 / 缺省返回空串(UI 据此不渲染)。 */
80
+ var HISTORY_SOURCE_LABELS = {
81
+ stage: "画布",
82
+ tree: "树面板",
83
+ "component-panel": "组件面板",
84
+ props: "配置面板",
85
+ code: "源码",
86
+ "root-code": "DSL源码",
87
+ "stage-contextmenu": "画布菜单",
88
+ "tree-contextmenu": "树菜单",
89
+ toolbar: "工具栏",
90
+ shortcut: "快捷键",
91
+ rollback: "回滚",
92
+ api: "接口",
93
+ ai: "AI",
94
+ initial: "初始值",
95
+ sync: "同步",
96
+ unknown: "未知"
97
+ };
98
+ /** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
99
+ var sourceLabel = (source = "unknown") => {
100
+ return HISTORY_SOURCE_LABELS[source] ?? `${source}`;
101
+ };
102
+ /** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
103
+ var groupSource = (group) => group.steps[group.steps.length - 1]?.step.source;
104
+ /**
105
+ * 把一个历史分组(页面 / bucket)派生为 GroupRow 直接消费的视图模型 {@link HistoryRowGroup}。
106
+ * 统一了原先 PageTab / Bucket 各自内联的 sub-steps 映射逻辑:描述、可差异、可回滚、时间、途径
107
+ * 全部在此一次性算好,组件层只负责渲染。
108
+ */
109
+ var toRowGroup = (group, key, descriptor) => {
110
+ const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
111
+ const timestamp = groupTimestamp(group);
112
+ return {
113
+ key,
114
+ applied: group.applied,
115
+ isCurrent: Boolean(group.isCurrent),
116
+ opType: group.opType,
117
+ desc: describeGroup(group),
118
+ source: groupSource(group),
119
+ time: formatHistoryTime(timestamp),
120
+ timeTitle: formatHistoryFullTime(timestamp),
121
+ subSteps: group.steps.map((s) => ({
122
+ index: s.index,
123
+ applied: s.applied,
124
+ isCurrent: s.isCurrent,
125
+ saved: s.step.saved,
126
+ desc: describeStep(s.step),
127
+ diffable: isStepDiffable ? isStepDiffable(s.step) : false,
128
+ revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
129
+ source: s.step.source,
130
+ time: formatHistoryTime(s.step.timestamp),
131
+ timeTitle: formatHistoryFullTime(s.step.timestamp)
132
+ }))
133
+ };
134
+ };
76
135
  var nameOf = (node) => node?.name || node?.type || `${node?.id ?? ""}`;
77
136
  /**
78
137
  * 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
@@ -91,24 +150,24 @@ var pickLastDescription = (descs) => {
91
150
  var describePageStep = (step) => {
92
151
  if (step.historyDescription) return step.historyDescription;
93
152
  const { opType } = step;
153
+ const items = step.diff ?? [];
94
154
  if (opType === "add") {
95
- const count = step.nodes?.length ?? 0;
96
- const node = step.nodes?.[0];
155
+ const count = items.length;
156
+ const node = items[0]?.newSchema;
97
157
  return `新增 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
98
158
  }
99
159
  if (opType === "remove") {
100
- const count = step.removedItems?.length ?? 0;
101
- const node = step.removedItems?.[0]?.node;
160
+ const count = items.length;
161
+ const node = items[0]?.oldSchema;
102
162
  return `删除 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ""}`;
103
163
  }
104
- const updated = step.updatedItems ?? [];
105
- if (!updated.length) return "修改节点";
106
- if (updated.length === 1) {
107
- const { newNode, changeRecords } = updated[0];
164
+ if (!items.length) return "修改节点";
165
+ if (items.length === 1) {
166
+ const { newSchema, changeRecords } = items[0];
108
167
  const propPath = changeRecords?.[0]?.propPath;
109
- return `修改 ${labelWithId(nameOf(newNode), newNode?.id)}${propPath ? ` · ${propPath}` : ""}`;
168
+ return `修改 ${labelWithId(nameOf(newSchema), newSchema?.id)}${propPath ? ` · ${propPath}` : ""}`;
110
169
  }
111
- return `修改 ${updated.length} 个节点`;
170
+ return `修改 ${items.length} 个节点`;
112
171
  };
113
172
  /**
114
173
  * 合并组的展示文案:
@@ -122,7 +181,7 @@ var describePageGroup = (group) => {
122
181
  if (group.steps.length === 1) return describePageStep(group.steps[0].step);
123
182
  const paths = /* @__PURE__ */ new Set();
124
183
  group.steps.forEach((s) => {
125
- s.step.updatedItems?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
184
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
126
185
  });
127
186
  const pathList = Array.from(paths).slice(0, 3).join(", ");
128
187
  const target = labelWithId(group.targetName ?? (group.targetId !== void 0 ? `${group.targetId}` : "节点"), group.targetId);
@@ -130,10 +189,11 @@ var describePageGroup = (group) => {
130
189
  };
131
190
  var describeDataSourceStep = (step) => {
132
191
  if (step.historyDescription) return step.historyDescription;
133
- if (step.oldSchema === null && step.newSchema) return `创建 ${labelWithId(step.newSchema.title, step.newSchema.id ?? step.id)}`;
134
- if (step.newSchema === null && step.oldSchema) return `删除 ${labelWithId(step.oldSchema.title, step.oldSchema.id ?? step.id)}`;
135
- const propPath = step.changeRecords?.[0]?.propPath;
136
- const title = labelWithId(step.newSchema?.title || step.oldSchema?.title, step.id);
192
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
193
+ if (!oldSchema && newSchema) return `创建 ${labelWithId(newSchema.title, newSchema.id ?? step.id)}`;
194
+ if (!newSchema && oldSchema) return `删除 ${labelWithId(oldSchema.title, oldSchema.id ?? step.id)}`;
195
+ const propPath = changeRecords?.[0]?.propPath;
196
+ const title = labelWithId(newSchema?.title || oldSchema?.title, step.id);
137
197
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
138
198
  };
139
199
  var describeDataSourceGroup = (group) => {
@@ -142,18 +202,19 @@ var describeDataSourceGroup = (group) => {
142
202
  if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
143
203
  const paths = /* @__PURE__ */ new Set();
144
204
  group.steps.forEach((s) => {
145
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
205
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
146
206
  });
147
207
  const pathList = Array.from(paths).slice(0, 3).join(", ");
148
- const target = labelWithId(group.steps[group.steps.length - 1].step.newSchema?.title || group.steps[0].step.oldSchema?.title, group.id);
208
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.title || group.steps[0].step.diff?.[0]?.oldSchema?.title, group.id);
149
209
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
150
210
  };
151
211
  var describeCodeBlockStep = (step) => {
152
212
  if (step.historyDescription) return step.historyDescription;
153
- if (step.oldContent === null && step.newContent) return `创建 ${labelWithId(step.newContent.name, step.newContent.id ?? step.id)}`;
154
- if (step.newContent === null && step.oldContent) return `删除 ${labelWithId(step.oldContent.name, step.oldContent.id ?? step.id)}`;
155
- const propPath = step.changeRecords?.[0]?.propPath;
156
- const title = labelWithId(step.newContent?.name || step.oldContent?.name, step.id);
213
+ const { oldSchema: oldContent, newSchema: newContent, changeRecords } = step.diff?.[0] ?? {};
214
+ if (!oldContent && newContent) return `创建 ${labelWithId(newContent.name, newContent.id ?? step.id)}`;
215
+ if (!newContent && oldContent) return `删除 ${labelWithId(oldContent.name, oldContent.id ?? step.id)}`;
216
+ const propPath = changeRecords?.[0]?.propPath;
217
+ const title = labelWithId(newContent?.name || oldContent?.name, step.id);
157
218
  return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
158
219
  };
159
220
  var describeCodeBlockGroup = (group) => {
@@ -162,11 +223,43 @@ var describeCodeBlockGroup = (group) => {
162
223
  if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
163
224
  const paths = /* @__PURE__ */ new Set();
164
225
  group.steps.forEach((s) => {
165
- s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
226
+ s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
166
227
  });
167
228
  const pathList = Array.from(paths).slice(0, 3).join(", ");
168
- const target = labelWithId(group.steps[group.steps.length - 1].step.newContent?.name || group.steps[0].step.oldContent?.name, group.id);
229
+ const target = labelWithId(group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.name || group.steps[0].step.diff?.[0]?.oldSchema?.name, group.id);
169
230
  return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? "…" : ""}` : `修改 ${target}`;
170
231
  };
232
+ /**
233
+ * 页面 step 是否支持「回滚」(类 git revert):
234
+ * - 新增 / 删除:不依赖 changeRecords,反向应用即删除 / 加回,始终可回滚;
235
+ * - 更新:必须每个被更新节点都带有 changeRecords,才支持按 propPath 局部反向 patch。
236
+ * 缺失 changeRecords 的更新只能整节点替换,会冲掉该节点后续的无关变更,因此不支持回滚。
237
+ */
238
+ var isPageStepRevertable = (step) => {
239
+ if (step.opType !== "update") return true;
240
+ const items = step.diff ?? [];
241
+ if (!items.length) return false;
242
+ return items.every((item) => Boolean(item.changeRecords?.length));
243
+ };
244
+ /**
245
+ * 数据源 step 是否支持「回滚」:
246
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
247
+ * - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
248
+ */
249
+ var isDataSourceStepRevertable = (step) => {
250
+ const item = step.diff?.[0];
251
+ if (!item?.oldSchema || !item?.newSchema) return true;
252
+ return Boolean(item.changeRecords?.length);
253
+ };
254
+ /**
255
+ * 代码块 step 是否支持「回滚」:
256
+ * - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
257
+ * - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
258
+ */
259
+ var isCodeBlockStepRevertable = (step) => {
260
+ const item = step.diff?.[0];
261
+ if (!item?.oldSchema || !item?.newSchema) return true;
262
+ return Boolean(item.changeRecords?.length);
263
+ };
171
264
  //#endregion
172
- export { describeCodeBlockGroup, describeCodeBlockStep, describeDataSourceGroup, describeDataSourceStep, describePageGroup, describePageStep, formatHistoryFullTime, formatHistoryTime, groupTimestamp, opLabel, useHistoryList };
265
+ export { describeCodeBlockGroup, describeCodeBlockStep, describeDataSourceGroup, describeDataSourceStep, describePageGroup, describePageStep, formatHistoryFullTime, formatHistoryTime, isCodeBlockStepRevertable, isDataSourceStepRevertable, isHistoryGroupExpanded, isPageStepRevertable, opLabel, sourceLabel, toRowGroup, useHistoryList };
@@ -4,7 +4,7 @@ import AddButton_default from "./AddButton.js";
4
4
  import PageBarScrollContainer_default from "./PageBarScrollContainer.js";
5
5
  import PageList_default from "./PageList.js";
6
6
  import Search_default from "./Search.js";
7
- import { TMagicIcon, TMagicPopover } from "@tmagic/design";
7
+ import { TMagicIcon, TMagicPopover, tMagicMessageBox } from "@tmagic/design";
8
8
  import { NodeType } from "@tmagic/core";
9
9
  import { Fragment, computed, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
10
10
  import { CaretBottom, Delete, DocumentCopy } from "@element-plus/icons-vue";
@@ -54,7 +54,8 @@ var PageBar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
54
54
  top: 0
55
55
  });
56
56
  };
57
- const remove = (node) => {
57
+ const remove = async (node) => {
58
+ await tMagicMessageBox.confirm("确定删除该页面吗?");
58
59
  editorService.remove(node);
59
60
  };
60
61
  const pageBarScrollContainerRef = useTemplateRef("pageBarScrollContainer");
@@ -121,6 +122,7 @@ var PageBar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
121
122
  type: "button",
122
123
  text: "删除",
123
124
  icon: unref(Delete),
125
+ buttonProps: { type: "danger" },
124
126
  handler: () => remove(item)
125
127
  } }, null, 8, ["data"])])])]),
126
128
  _: 2
@@ -69,7 +69,11 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
69
69
  if (record.propPath?.startsWith("style") && record.value === "") setValueByKeyPath(record.propPath, record.value, newValue);
70
70
  });
71
71
  }
72
- editorService.update(newValue, { changeRecords: eventData?.changeRecords });
72
+ const historySource = eventData ? "props" : "code";
73
+ editorService.update(newValue, {
74
+ changeRecords: eventData?.changeRecords,
75
+ historySource
76
+ });
73
77
  } catch (e) {
74
78
  emit("submit-error", e);
75
79
  }
@@ -37,7 +37,7 @@ var ComponentListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE_
37
37
  name: text,
38
38
  type,
39
39
  ...data
40
- });
40
+ }, void 0, { historySource: "component-panel" });
41
41
  };
42
42
  const dragstartHandler = ({ text, type, data = {} }, e) => {
43
43
  e.dataTransfer?.setData("text/json", serialize({
@@ -71,7 +71,11 @@ var Sidebar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
71
71
  });
72
72
  }
73
73
  }, { immediate: true });
74
- const activeTabName = ref(props.data?.status);
74
+ const activeTabName = computed({
75
+ get: () => uiService.get("sideBarActiveTabName"),
76
+ set: (value) => uiService.set("sideBarActiveTabName", value)
77
+ });
78
+ uiService.set("sideBarActiveTabName", props.data?.status || "");
75
79
  const getItemConfig = (data) => {
76
80
  const map = {
77
81
  [SideItemKey.COMPONENT_LIST]: {
@@ -78,7 +78,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
78
78
  const editCode = (id) => {
79
79
  emit("edit", id);
80
80
  };
81
- const deleteCode = async (id) => {
81
+ const deleteCode = async (id, { historySource } = {}) => {
82
82
  const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
83
83
  const existBinds = Boolean(currentCode?.items?.length);
84
84
  const undeleteableList = codeBlockService.getUndeletableList() || [];
@@ -88,7 +88,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
88
88
  cancelButtonText: "取消",
89
89
  type: "warning"
90
90
  });
91
- emit("remove", id);
91
+ emit("remove", id, { historySource });
92
92
  } else if (typeof props.customError === "function") props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
93
93
  else if (existBinds) tMagicMessage.error("代码块存在绑定关系,不可删除");
94
94
  else tMagicMessage.error("代码块不可删除");
@@ -138,7 +138,7 @@ var CodeBlockList_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
138
138
  default: withCtx(() => [createVNode(Icon_default, {
139
139
  icon: unref(Close),
140
140
  class: "edit-icon",
141
- onClick: withModifiers(($event) => deleteCode(`${data.key}`), ["stop"])
141
+ onClick: withModifiers(($event) => deleteCode(`${data.key}`, { historySource: "tree" }), ["stop"])
142
142
  }, null, 8, ["icon", "onClick"])]),
143
143
  _: 2
144
144
  }, 1024)) : createCommentVNode("v-if", true),
@@ -38,7 +38,7 @@ var CodeBlockListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE_
38
38
  if (codeBlockListRef.value) for (const [, status] of codeBlockListRef.value.nodeStatusMap.entries()) status.selected = false;
39
39
  };
40
40
  const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu((id) => {
41
- codeBlockListRef.value?.deleteCode(id);
41
+ codeBlockListRef.value?.deleteCode(id, { historySource: "tree-contextmenu" });
42
42
  });
43
43
  const menuData = computed(() => props.customContentMenu(contentMenuData, "code-block"));
44
44
  return (_ctx, _cache) => {
@@ -26,7 +26,7 @@ var useContentMenu = (deleteCode) => {
26
26
  const codeBlock = codeBlockService.getCodeContentById(selectId);
27
27
  if (!codeBlock) return;
28
28
  const newCodeId = await codeBlockService.getUniqueId();
29
- codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock));
29
+ codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock), { historySource: "tree-contextmenu" });
30
30
  }
31
31
  },
32
32
  {
@@ -55,7 +55,7 @@ var DataSourceListPanel_vue_vue_type_script_setup_true_lang_default = /* @__PURE
55
55
  cancelButtonText: "取消",
56
56
  type: "warning"
57
57
  });
58
- dataSourceService.remove(id);
58
+ dataSourceService.remove(id, { historySource: "tree-contextmenu" });
59
59
  };
60
60
  const dataSourceListRef = useTemplateRef("dataSourceList");
61
61
  const filterTextChangeHandler = (val) => {
@@ -25,7 +25,7 @@ var useContentMenu = () => {
25
25
  if (!selectId) return;
26
26
  const ds = dataSourceService.getDataSourceById(selectId);
27
27
  if (!ds) return;
28
- dataSourceService.add(cloneDeep(ds));
28
+ dataSourceService.add(cloneDeep(ds), { historySource: "tree-contextmenu" });
29
29
  }
30
30
  },
31
31
  {
@@ -32,7 +32,7 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
32
32
  name: component.text,
33
33
  type: component.type,
34
34
  ...component.data || {}
35
- });
35
+ }, void 0, { historySource: "tree-contextmenu" });
36
36
  }
37
37
  }));
38
38
  const getSubMenuData = computed(() => {
@@ -41,7 +41,7 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
41
41
  type: "button",
42
42
  icon: Files,
43
43
  handler: () => {
44
- editorService.add({ type: "tab-pane" });
44
+ editorService.add({ type: "tab-pane" }, void 0, { historySource: "tree-contextmenu" });
45
45
  }
46
46
  }];
47
47
  if (node.value?.items) return componentList.value.reduce((subMenuData, group, index) => subMenuData.concat(createMenuItems(group), index < componentList.value.length - 1 ? [{
@@ -68,9 +68,9 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
68
68
  items: getSubMenuData.value
69
69
  },
70
70
  useCopyMenu(),
71
- usePasteMenu(),
72
- useDeleteMenu(),
73
- useMoveToMenu(services),
71
+ usePasteMenu("tree-contextmenu"),
72
+ useDeleteMenu("tree-contextmenu"),
73
+ useMoveToMenu(services, "tree-contextmenu"),
74
74
  ...props.layerContentMenu
75
75
  ], "layer"));
76
76
  const show = (e) => {
@@ -13,7 +13,7 @@ var LayerNodeTool_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
13
13
  editorService.update({
14
14
  id: props.data.id,
15
15
  visible
16
- });
16
+ }, { historySource: "tree" });
17
17
  };
18
18
  return (_ctx, _cache) => {
19
19
  return __props.data.type !== "page" ? (openBlock(), createBlock(unref(TMagicButton), {
@@ -226,7 +226,7 @@ var Stage_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
226
226
  left: calcValueByFontsize(doc, left / zoom.value)
227
227
  };
228
228
  config.data.inputEvent = e;
229
- editorService.add(config.data, parent);
229
+ editorService.add(config.data, parent, { historySource: "component-panel" });
230
230
  }
231
231
  };
232
232
  return (_ctx, _cache) => {