@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
@@ -36,11 +36,11 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
36
36
  display: () => canCenter.value,
37
37
  handler: () => {
38
38
  if (!nodes.value) return;
39
- editorService.alignCenter(nodes.value);
39
+ editorService.alignCenter(nodes.value, { historySource: "stage-contextmenu" });
40
40
  }
41
41
  },
42
42
  useCopyMenu(),
43
- usePasteMenu(menuRef),
43
+ usePasteMenu("stage-contextmenu", menuRef),
44
44
  {
45
45
  type: "divider",
46
46
  direction: "horizontal",
@@ -55,7 +55,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
55
55
  icon: markRaw(Top),
56
56
  display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
57
57
  handler: () => {
58
- editorService.moveLayer(1);
58
+ editorService.moveLayer(1, { historySource: "stage-contextmenu" });
59
59
  }
60
60
  },
61
61
  {
@@ -64,7 +64,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
64
64
  icon: markRaw(Bottom),
65
65
  display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
66
66
  handler: () => {
67
- editorService.moveLayer(-1);
67
+ editorService.moveLayer(-1, { historySource: "stage-contextmenu" });
68
68
  }
69
69
  },
70
70
  {
@@ -73,7 +73,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
73
73
  icon: markRaw(Top),
74
74
  display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
75
75
  handler: () => {
76
- editorService.moveLayer(LayerOffset.TOP);
76
+ editorService.moveLayer(LayerOffset.TOP, { historySource: "stage-contextmenu" });
77
77
  }
78
78
  },
79
79
  {
@@ -82,16 +82,16 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
82
82
  icon: markRaw(Bottom),
83
83
  display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
84
84
  handler: () => {
85
- editorService.moveLayer(LayerOffset.BOTTOM);
85
+ editorService.moveLayer(LayerOffset.BOTTOM, { historySource: "stage-contextmenu" });
86
86
  }
87
87
  },
88
- useMoveToMenu(services),
88
+ useMoveToMenu(services, "stage-contextmenu"),
89
89
  {
90
90
  type: "divider",
91
91
  direction: "horizontal",
92
92
  display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect
93
93
  },
94
- useDeleteMenu(),
94
+ useDeleteMenu("stage-contextmenu"),
95
95
  {
96
96
  type: "divider",
97
97
  direction: "horizontal"
@@ -1,7 +1,8 @@
1
1
  import { CODE_DRAFT_STORAGE_KEY } from "../type.js";
2
2
  import BaseService from "./BaseService.js";
3
- import history_default from "./history.js";
4
3
  import { getEditorConfig } from "../utils/config.js";
4
+ import { describeRevertStep } from "../utils/history.js";
5
+ import history_default from "./history.js";
5
6
  import storage_default, { Protocol } from "./storage.js";
6
7
  import { COPY_CODE_STORAGE_KEY } from "../utils/editor.js";
7
8
  import editor_default from "./editor.js";
@@ -20,17 +21,6 @@ var canUsePluginMethods = {
20
21
  ],
21
22
  sync: ["setCodeDslByIdSync"]
22
23
  };
23
- /**
24
- * 「回滚」生成的新 step 简短描述。仅 service 层使用。
25
- */
26
- var describeRevertCodeBlockStep = (step) => {
27
- const { oldContent, newContent, changeRecords, id } = step;
28
- if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
29
- if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
30
- const name = newContent?.name || oldContent?.name || `${id}`;
31
- const propPath = changeRecords?.[0]?.propPath;
32
- return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
33
- };
34
24
  var CodeBlock = class extends BaseService {
35
25
  state = reactive({
36
26
  codeDsl: null,
@@ -39,6 +29,16 @@ var CodeBlock = class extends BaseService {
39
29
  undeletableList: [],
40
30
  paramsColConfig: void 0
41
31
  });
32
+ /**
33
+ * 最近一次写入历史栈的代码块历史记录 uuid(单条写入场景:新增 / 更新)。
34
+ * 供 setCodeDslById(Sync)AndGetHistoryId 取回,普通方法不读取它。
35
+ */
36
+ lastPushedHistoryId = null;
37
+ /**
38
+ * deleteCodeDslByIds 一次删除多个代码块时,按写入顺序收集的历史记录 uuid 列表。
39
+ * 在 deleteCodeDslByIds 入口处重置,供 deleteCodeDslByIdsAndGetHistoryId 取回。
40
+ */
41
+ lastDeletedHistoryIds = [];
42
42
  constructor() {
43
43
  super([...canUsePluginMethods.async.map((methodName) => ({
44
44
  name: methodName,
@@ -86,10 +86,12 @@ var CodeBlock = class extends BaseService {
86
86
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
87
87
  * @returns {void}
88
88
  */
89
- async setCodeDslById(id, codeConfig, { changeRecords, doNotPushHistory = false } = {}) {
89
+ async setCodeDslById(id, codeConfig, { changeRecords, doNotPushHistory = false, historyDescription, historySource } = {}) {
90
90
  this.setCodeDslByIdSync(id, codeConfig, true, {
91
91
  changeRecords,
92
- doNotPushHistory
92
+ doNotPushHistory,
93
+ historyDescription,
94
+ historySource
93
95
  });
94
96
  }
95
97
  /**
@@ -104,7 +106,7 @@ var CodeBlock = class extends BaseService {
104
106
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
105
107
  * @returns {void}
106
108
  */
107
- setCodeDslByIdSync(id, codeConfig, force = true, { changeRecords, doNotPushHistory = false, historyDescription } = {}) {
109
+ setCodeDslByIdSync(id, codeConfig, force = true, { changeRecords, doNotPushHistory = false, historyDescription, historySource } = {}) {
108
110
  const codeDsl = this.getCodeDsl();
109
111
  if (!codeDsl) throw new Error("dsl中没有codeBlocks");
110
112
  if (codeDsl[id] && !force) return;
@@ -119,12 +121,13 @@ var CodeBlock = class extends BaseService {
119
121
  ...codeConfigProcessed
120
122
  };
121
123
  const newContent = cloneDeep$1(codeDsl[id]);
122
- if (!doNotPushHistory) history_default.pushCodeBlock(id, {
124
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushCodeBlock(id, {
123
125
  oldContent,
124
126
  newContent,
125
127
  changeRecords,
126
- historyDescription
127
- });
128
+ historyDescription,
129
+ source: historySource
130
+ })?.uuid ?? null;
128
131
  this.emit("addOrUpdate", id, codeDsl[id]);
129
132
  }
130
133
  /**
@@ -204,20 +207,53 @@ var CodeBlock = class extends BaseService {
204
207
  * @param options 可选配置
205
208
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
206
209
  */
207
- async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription } = {}) {
210
+ async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription, historySource } = {}) {
208
211
  const currentDsl = await this.getCodeDsl();
209
212
  if (!currentDsl) return;
213
+ this.lastDeletedHistoryIds = [];
210
214
  codeIds.forEach((id) => {
211
215
  const oldContent = currentDsl[id] ? cloneDeep$1(currentDsl[id]) : null;
212
216
  delete currentDsl[id];
213
- if (oldContent && !doNotPushHistory) history_default.pushCodeBlock(id, {
214
- oldContent,
215
- newContent: null,
216
- historyDescription
217
- });
217
+ if (oldContent && !doNotPushHistory) {
218
+ const uuid = history_default.pushCodeBlock(id, {
219
+ oldContent,
220
+ newContent: null,
221
+ historyDescription,
222
+ source: historySource
223
+ })?.uuid;
224
+ if (uuid) this.lastDeletedHistoryIds.push(uuid);
225
+ }
218
226
  this.emit("remove", id);
219
227
  });
220
228
  }
229
+ /**
230
+ * 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
231
+ * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link CodeBlockStepValue.uuid}),
232
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
233
+ *
234
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时:单条写入返回 null,批量删除返回空数组。
235
+ */
236
+ /** 等价于 {@link setCodeDslById},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
237
+ async setCodeDslByIdAndGetHistoryId(id, codeConfig, options = {}) {
238
+ this.lastPushedHistoryId = null;
239
+ await this.setCodeDslById(id, codeConfig, options);
240
+ return this.lastPushedHistoryId;
241
+ }
242
+ /** 等价于 {@link setCodeDslByIdSync},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
243
+ setCodeDslByIdSyncAndGetHistoryId(id, codeConfig, force = true, options = {}) {
244
+ this.lastPushedHistoryId = null;
245
+ this.setCodeDslByIdSync(id, codeConfig, force, options);
246
+ return this.lastPushedHistoryId;
247
+ }
248
+ /**
249
+ * 等价于 {@link deleteCodeDslByIds},但返回本次写入的全部历史记录 uuid(按删除顺序)。
250
+ * 一次删除多个代码块会产生多条历史记录,因此返回数组;未写入任何历史时返回空数组。
251
+ */
252
+ async deleteCodeDslByIdsAndGetHistoryId(codeIds, options = {}) {
253
+ this.lastDeletedHistoryIds = [];
254
+ await this.deleteCodeDslByIds(codeIds, options);
255
+ return [...this.lastDeletedHistoryIds];
256
+ }
221
257
  setParamsColConfig(config) {
222
258
  this.state.paramsColConfig = config;
223
259
  }
@@ -293,10 +329,25 @@ var CodeBlock = class extends BaseService {
293
329
  async revert(id, index) {
294
330
  const entry = history_default.getCodeBlockStepList(id)[index];
295
331
  if (!entry?.applied) return null;
296
- const description = `回滚 #${index + 1}: ${describeRevertCodeBlockStep(entry.step)}`;
332
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
333
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
334
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.name)}`;
297
335
  return await this.applyRevertStep(entry.step, description);
298
336
  }
299
337
  /**
338
+ * 通过历史记录 uuid 回滚某条代码块历史步骤,语义同 {@link revert},
339
+ * 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid({@link CodeBlockStepValue.uuid})
340
+ * 在全部代码块栈中定位对应步骤后再回滚。
341
+ *
342
+ * @param uuid 目标历史记录的 uuid,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回
343
+ * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
344
+ */
345
+ async revertById(uuid) {
346
+ const location = history_default.findCodeBlockStepLocationByUuid(uuid);
347
+ if (!location) return null;
348
+ return await this.revert(location.id, location.index);
349
+ }
350
+ /**
300
351
  * 生成代码块唯一id
301
352
  * @returns {Id} 代码块唯一id
302
353
  */
@@ -361,16 +412,23 @@ var CodeBlock = class extends BaseService {
361
412
  * 差异仅在于通过公开的 setCodeDslByIdSync / deleteCodeDslByIds 触发 push。
362
413
  */
363
414
  async applyRevertStep(step, historyDescription) {
364
- const { id, oldContent, newContent, changeRecords } = step;
365
- if (oldContent === null && newContent) {
366
- await this.deleteCodeDslByIds([id], { historyDescription });
415
+ const { id } = step;
416
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
417
+ if (!oldSchema && newSchema) {
418
+ await this.deleteCodeDslByIds([id], {
419
+ historyDescription,
420
+ historySource: "rollback"
421
+ });
367
422
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
368
423
  }
369
- if (oldContent && newContent === null) {
370
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
424
+ if (oldSchema && !newSchema) {
425
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
426
+ historyDescription,
427
+ historySource: "rollback"
428
+ });
371
429
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
372
430
  }
373
- if (!oldContent || !newContent) return null;
431
+ if (!oldSchema || !newSchema) return null;
374
432
  if (changeRecords?.length) {
375
433
  const current = this.getCodeContentById(id);
376
434
  if (!current) return null;
@@ -381,16 +439,20 @@ var CodeBlock = class extends BaseService {
381
439
  fallbackToFullReplace = true;
382
440
  break;
383
441
  }
384
- const value = cloneDeep$1(getValueByKeyPath(record.propPath, oldContent));
442
+ const value = cloneDeep$1(getValueByKeyPath(record.propPath, oldSchema));
385
443
  setValueByKeyPath(record.propPath, value, patched);
386
444
  }
387
- this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldContent) : patched, true, {
445
+ this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, true, {
388
446
  changeRecords,
389
- historyDescription
447
+ historyDescription,
448
+ historySource: "rollback"
390
449
  });
391
450
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
392
451
  }
393
- this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
452
+ this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, {
453
+ historyDescription,
454
+ historySource: "rollback"
455
+ });
394
456
  return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
395
457
  }
396
458
  /**
@@ -408,19 +470,20 @@ var CodeBlock = class extends BaseService {
408
470
  * @param reverse true=撤销,false=重做
409
471
  */
410
472
  async applyHistoryStep(step, reverse) {
411
- const { id, oldContent, newContent, changeRecords } = step;
412
- if (oldContent === null && newContent) {
473
+ const { id } = step;
474
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
475
+ if (!oldSchema && newSchema) {
413
476
  if (reverse) await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
414
- else this.setCodeDslByIdSync(id, cloneDeep$1(newContent), true, { doNotPushHistory: true });
477
+ else this.setCodeDslByIdSync(id, cloneDeep$1(newSchema), true, { doNotPushHistory: true });
415
478
  return;
416
479
  }
417
- if (oldContent && newContent === null) {
418
- if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { doNotPushHistory: true });
480
+ if (oldSchema && !newSchema) {
481
+ if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldSchema), true, { doNotPushHistory: true });
419
482
  else await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
420
483
  return;
421
484
  }
422
- if (!oldContent || !newContent) return;
423
- const sourceForValues = reverse ? oldContent : newContent;
485
+ if (!oldSchema || !newSchema) return;
486
+ const sourceForValues = reverse ? oldSchema : newSchema;
424
487
  if (changeRecords?.length) {
425
488
  const current = this.getCodeContentById(id);
426
489
  if (!current) return;
@@ -1,4 +1,5 @@
1
1
  import BaseService from "./BaseService.js";
2
+ import { describeRevertStep } from "../utils/history.js";
2
3
  import history_default from "./history.js";
3
4
  import storage_default, { Protocol } from "./storage.js";
4
5
  import { COPY_DS_STORAGE_KEY } from "../utils/editor.js";
@@ -26,18 +27,6 @@ var canUsePluginMethods = {
26
27
  "createId"
27
28
  ]
28
29
  };
29
- /**
30
- * 「回滚」生成的新 step 简短描述。
31
- * 仅在 service 层使用,避免依赖 UI 层 composables。
32
- */
33
- var describeRevertDataSourceStep = (step) => {
34
- const { oldSchema, newSchema, changeRecords, id } = step;
35
- if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
36
- if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
37
- const title = newSchema?.title || oldSchema?.title || `${id}`;
38
- const propPath = changeRecords?.[0]?.propPath;
39
- return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
40
- };
41
30
  var DataSource = class extends BaseService {
42
31
  state = reactive({
43
32
  datasourceTypeList: [],
@@ -48,6 +37,12 @@ var DataSource = class extends BaseService {
48
37
  events: {},
49
38
  methods: {}
50
39
  });
40
+ /**
41
+ * 最近一次写入历史栈的数据源历史记录 uuid。
42
+ * 供 *AndGetHistoryId 系列方法在调用 add / update / remove 后取回本次产生的历史记录 id;
43
+ * 普通方法不读取它,调用前由 *AndGetHistoryId 重置为 null。
44
+ */
45
+ lastPushedHistoryId = null;
51
46
  constructor() {
52
47
  super(canUsePluginMethods.sync.map((methodName) => ({
53
48
  name: methodName,
@@ -91,17 +86,18 @@ var DataSource = class extends BaseService {
91
86
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
92
87
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
93
88
  */
94
- add(config, { doNotPushHistory = false, historyDescription } = {}) {
89
+ add(config, { doNotPushHistory = false, historyDescription, historySource } = {}) {
95
90
  const newConfig = {
96
91
  ...config,
97
92
  id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId()
98
93
  };
99
94
  this.get("dataSources").push(newConfig);
100
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
95
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
101
96
  oldSchema: null,
102
97
  newSchema: newConfig,
103
- historyDescription
104
- });
98
+ historyDescription,
99
+ source: historySource
100
+ })?.uuid ?? null;
105
101
  this.emit("add", newConfig);
106
102
  return newConfig;
107
103
  }
@@ -113,18 +109,19 @@ var DataSource = class extends BaseService {
113
109
  * @param data.doNotPushHistory 是否不写入历史记录(默认 false)
114
110
  * @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
115
111
  */
116
- update(config, { changeRecords = [], doNotPushHistory = false, historyDescription } = {}) {
112
+ update(config, { changeRecords = [], doNotPushHistory = false, historyDescription, historySource } = {}) {
117
113
  const dataSources = this.get("dataSources");
118
114
  const index = dataSources.findIndex((ds) => ds.id === config.id);
119
115
  const oldConfig = dataSources[index];
120
116
  const newConfig = cloneDeep$1(config);
121
117
  dataSources[index] = newConfig;
122
- if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
118
+ if (!doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(newConfig.id, {
123
119
  oldSchema: oldConfig ? cloneDeep$1(oldConfig) : null,
124
120
  newSchema: newConfig,
125
121
  changeRecords,
126
- historyDescription
127
- });
122
+ historyDescription,
123
+ source: historySource
124
+ })?.uuid ?? null;
128
125
  this.emit("update", newConfig, {
129
126
  oldConfig,
130
127
  changeRecords
@@ -138,19 +135,45 @@ var DataSource = class extends BaseService {
138
135
  * @param options.doNotPushHistory 是否不写入历史记录(默认 false)
139
136
  * @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
140
137
  */
141
- remove(id, { doNotPushHistory = false, historyDescription } = {}) {
138
+ remove(id, { doNotPushHistory = false, historyDescription, historySource } = {}) {
142
139
  const dataSources = this.get("dataSources");
143
140
  const index = dataSources.findIndex((ds) => ds.id === id);
144
141
  const oldConfig = index !== -1 ? dataSources[index] : null;
145
142
  dataSources.splice(index, 1);
146
- if (oldConfig && !doNotPushHistory) history_default.pushDataSource(id, {
143
+ if (oldConfig && !doNotPushHistory) this.lastPushedHistoryId = history_default.pushDataSource(id, {
147
144
  oldSchema: cloneDeep$1(oldConfig),
148
145
  newSchema: null,
149
- historyDescription
150
- });
146
+ historyDescription,
147
+ source: historySource
148
+ })?.uuid ?? null;
151
149
  this.emit("remove", id);
152
150
  }
153
151
  /**
152
+ * 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
153
+ * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link DataSourceStepValue.uuid}),
154
+ * 而非数据源配置。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
155
+ *
156
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时返回 null。
157
+ */
158
+ /** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
159
+ addAndGetHistoryId(config, options = {}) {
160
+ this.lastPushedHistoryId = null;
161
+ this.add(config, options);
162
+ return this.lastPushedHistoryId;
163
+ }
164
+ /** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
165
+ updateAndGetHistoryId(config, options = {}) {
166
+ this.lastPushedHistoryId = null;
167
+ this.update(config, options);
168
+ return this.lastPushedHistoryId;
169
+ }
170
+ /** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
171
+ removeAndGetHistoryId(id, options = {}) {
172
+ this.lastPushedHistoryId = null;
173
+ this.remove(id, options);
174
+ return this.lastPushedHistoryId;
175
+ }
176
+ /**
154
177
  * 撤销指定数据源的最近一次变更。
155
178
  *
156
179
  * 内部走 add / update / remove,因此会自动触发 dataSourceService 的事件,
@@ -218,9 +241,24 @@ var DataSource = class extends BaseService {
218
241
  revert(id, index) {
219
242
  const entry = history_default.getDataSourceStepList(id)[index];
220
243
  if (!entry?.applied) return null;
221
- const description = `回滚 #${index + 1}: ${describeRevertDataSourceStep(entry.step)}`;
244
+ const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
245
+ if (oldSchema && newSchema && !changeRecords?.length) return null;
246
+ const description = `回滚 #${index + 1}: ${describeRevertStep(entry.step.id, entry.step.diff?.[0], (s) => s.title)}`;
222
247
  return this.applyRevertStep(entry.step, description);
223
248
  }
249
+ /**
250
+ * 通过历史记录 uuid 回滚某条数据源历史步骤,语义同 {@link revert},
251
+ * 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid({@link DataSourceStepValue.uuid})
252
+ * 在全部数据源栈中定位对应步骤后再回滚。
253
+ *
254
+ * @param uuid 目标历史记录的 uuid,通常由 {@link addAndGetHistoryId} 等方法返回
255
+ * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
256
+ */
257
+ revertById(uuid) {
258
+ const location = history_default.findDataSourceStepLocationByUuid(uuid);
259
+ if (!location) return null;
260
+ return this.revert(location.id, location.index);
261
+ }
224
262
  createId() {
225
263
  return `ds_${guid()}`;
226
264
  }
@@ -279,13 +317,20 @@ var DataSource = class extends BaseService {
279
317
  * 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
280
318
  */
281
319
  applyRevertStep(step, historyDescription) {
282
- const { id, oldSchema, newSchema, changeRecords } = step;
283
- if (oldSchema === null && newSchema) {
284
- this.remove(`${id}`, { historyDescription });
320
+ const { id } = step;
321
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
322
+ if (!oldSchema && newSchema) {
323
+ this.remove(`${id}`, {
324
+ historyDescription,
325
+ historySource: "rollback"
326
+ });
285
327
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
286
328
  }
287
- if (oldSchema && newSchema === null) {
288
- this.add(cloneDeep$1(oldSchema), { historyDescription });
329
+ if (oldSchema && !newSchema) {
330
+ this.add(cloneDeep$1(oldSchema), {
331
+ historyDescription,
332
+ historySource: "rollback"
333
+ });
289
334
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
290
335
  }
291
336
  if (!oldSchema || !newSchema) return null;
@@ -304,11 +349,15 @@ var DataSource = class extends BaseService {
304
349
  }
305
350
  this.update(fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, {
306
351
  changeRecords,
307
- historyDescription
352
+ historyDescription,
353
+ historySource: "rollback"
308
354
  });
309
355
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
310
356
  }
311
- this.update(cloneDeep$1(oldSchema), { historyDescription });
357
+ this.update(cloneDeep$1(oldSchema), {
358
+ historyDescription,
359
+ historySource: "rollback"
360
+ });
312
361
  return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
313
362
  }
314
363
  /**
@@ -326,13 +375,14 @@ var DataSource = class extends BaseService {
326
375
  * @param reverse true=撤销,false=重做
327
376
  */
328
377
  applyHistoryStep(step, reverse) {
329
- const { id, oldSchema, newSchema, changeRecords } = step;
330
- if (oldSchema === null && newSchema) {
378
+ const { id } = step;
379
+ const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
380
+ if (!oldSchema && newSchema) {
331
381
  if (reverse) this.remove(`${id}`, { doNotPushHistory: true });
332
382
  else this.add(cloneDeep$1(newSchema), { doNotPushHistory: true });
333
383
  return;
334
384
  }
335
- if (oldSchema && newSchema === null) {
385
+ if (oldSchema && !newSchema) {
336
386
  if (reverse) this.add(cloneDeep$1(oldSchema), { doNotPushHistory: true });
337
387
  else this.remove(`${id}`, { doNotPushHistory: true });
338
388
  return;