@tmagic/editor 1.8.0-manmanyu.13 → 1.8.0-manmanyu.14

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.
@@ -1,4 +1,3 @@
1
- import { useServices } from "../hooks/use-services.js";
2
1
  import { getCodeBlockFormConfig } from "../utils/code-block.js";
3
2
  import { MForm } from "@tmagic/form";
4
3
  import { HookType } from "@tmagic/core";
@@ -23,11 +22,10 @@ var CompareForm_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
23
22
  baseFormState: {},
24
23
  selfDiffFieldTypes: {},
25
24
  loadConfig: {},
26
- services: { default: () => useServices() }
25
+ services: {}
27
26
  },
28
27
  setup(__props, { expose: __expose }) {
29
28
  const props = __props;
30
- const { propsService, dataSourceService, codeBlockService, editorService } = props.services;
31
29
  provide("services", props.services);
32
30
  const config = ref([]);
33
31
  /** vs-code 编辑器的 monaco 配置项,沿用 Editor 顶层 provide('codeOptions', ...) 的注入。 */
@@ -107,16 +105,17 @@ var CompareForm_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
107
105
  * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
108
106
  */
109
107
  const defaultLoadConfig = async () => {
108
+ if (!props.services) return [];
110
109
  switch (props.category) {
111
110
  case "node":
112
111
  if (!props.type) return [];
113
- return removeStyleDisplayConfig(await propsService.getPropsConfig(props.type, { node: props.value }));
114
- case "data-source": return dataSourceService.getFormConfig(props.type || "base").map((item) => "type" in item && item.type === "tab" ? {
112
+ return removeStyleDisplayConfig(await props.services.propsService.getPropsConfig(props.type, { node: props.value }));
113
+ case "data-source": return props.services.dataSourceService.getFormConfig(props.type || "base").map((item) => "type" in item && item.type === "tab" ? {
115
114
  ...item,
116
115
  active: "fields"
117
116
  } : item);
118
117
  case "code-block": return getCodeBlockFormConfig({
119
- paramColConfig: codeBlockService.getParamsColConfig(),
118
+ paramColConfig: props.services.codeBlockService.getParamsColConfig(),
120
119
  isDataSource: () => Boolean(props.dataSourceType),
121
120
  dataSourceType: () => props.dataSourceType,
122
121
  codeOptions,
@@ -155,10 +154,9 @@ var CompareForm_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
155
154
  * - services:整个 useServices() 返回的服务集合;
156
155
  * - stage:当前 editorService.get('stage') 的最新值。
157
156
  */
158
- const stage = computed(() => editorService.get("stage"));
159
157
  watchEffect(() => {
160
- if (formRef.value) {
161
- formRef.value.formState.stage = stage.value;
158
+ if (formRef.value && props.services) {
159
+ formRef.value.formState.stage = props.services.editorService.get("stage");
162
160
  formRef.value.formState.services = props.services;
163
161
  }
164
162
  });
@@ -1,12 +1,15 @@
1
1
  import Bucket_default from "./Bucket.js";
2
2
  import { TMagicScrollbar } from "@tmagic/design";
3
- import { Fragment, createBlock, createElementBlock, createElementVNode, createVNode, defineComponent, openBlock, renderList, unref, withCtx } from "vue";
3
+ import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, openBlock, renderList, unref, withCtx } from "vue";
4
4
  //#region packages/editor/src/layouts/history-list/BucketTab.vue?vue&type=script&setup=true&lang.ts
5
5
  var _hoisted_1 = {
6
6
  key: 0,
7
7
  class: "m-editor-history-list-empty"
8
8
  };
9
- var _hoisted_2 = { class: "m-editor-history-list-toolbar" };
9
+ var _hoisted_2 = {
10
+ key: 0,
11
+ class: "m-editor-history-list-toolbar"
12
+ };
10
13
  var _hoisted_3 = ["title"];
11
14
  var BucketTab_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
12
15
  name: "MEditorHistoryListBucketTab",
@@ -26,11 +29,11 @@ var BucketTab_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
26
29
  ],
27
30
  setup(__props) {
28
31
  return (_ctx, _cache) => {
29
- return !__props.buckets.length ? (openBlock(), createElementBlock("div", _hoisted_1, "暂无操作记录")) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", _hoisted_2, [createElementVNode("span", {
32
+ return !__props.buckets.length ? (openBlock(), createElementBlock("div", _hoisted_1, "暂无操作记录")) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [__props.config.showClear !== false ? (openBlock(), createElementBlock("div", _hoisted_2, [createElementVNode("span", {
30
33
  class: "m-editor-history-list-clear",
31
34
  title: `清空${__props.config.title}的历史记录`,
32
35
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("clear"))
33
- }, "清空", 8, _hoisted_3)]), createVNode(unref(TMagicScrollbar), { "max-height": "360px" }, {
36
+ }, "清空", 8, _hoisted_3)])) : createCommentVNode("v-if", true), createVNode(unref(TMagicScrollbar), { "max-height": "360px" }, {
34
37
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.buckets, (bucket) => {
35
38
  return openBlock(), createBlock(Bucket_default, {
36
39
  key: `${__props.config.prefix}-${bucket.id}`,
@@ -1,4 +1,3 @@
1
- import { useServices } from "../../hooks/use-services.js";
2
1
  import CodeEditor_default from "../CodeEditor.js";
3
2
  import CompareForm_default from "../../components/CompareForm.js";
4
3
  import { TMagicButton, TMagicDialog, TMagicRadioButton, TMagicRadioGroup, TMagicTag } from "@tmagic/design";
@@ -25,7 +24,7 @@ var HistoryDiffDialog_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
25
24
  name: "MEditorHistoryDiffDialog",
26
25
  __name: "HistoryDiffDialog",
27
26
  props: {
28
- services: { default: () => useServices() },
27
+ services: {},
29
28
  extendState: {},
30
29
  loadConfig: {},
31
30
  width: { default: "900px" },
@@ -66,6 +65,20 @@ var HistoryDiffDialog_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
66
65
  };
67
66
  const dialogTitle = computed(() => props.onConfirm ? "确认回滚" : "查看修改差异");
68
67
  const hasCurrent = computed(() => payload.value?.currentValue !== void 0 && payload.value?.currentValue !== null);
68
+ /** 是否存在该步「修改后的值」:不存在(如仅删除)时「与修改前对比」无意义,置灰禁用。 */
69
+ const hasValue = computed(() => payload.value?.value !== void 0 && payload.value?.value !== null);
70
+ /** 指定模式当前是否可用:before 依赖「修改后的值」,current 依赖「当前值」。 */
71
+ const isModeAvailable = (m) => m === "current" ? hasCurrent.value : hasValue.value;
72
+ /**
73
+ * 计算 open 时的初始对比模式:
74
+ * - 调用方通过 payload.mode 指定且该模式可用时,优先使用指定模式;
75
+ * - 否则没有「修改后的值」但有当前值时(「与修改前对比」不可用),默认进入「与当前对比」;
76
+ * - 其余情况默认「与修改前对比」。
77
+ */
78
+ const resolveInitialMode = (specified) => {
79
+ if (specified && isModeAvailable(specified)) return specified;
80
+ return !hasValue.value && hasCurrent.value ? "current" : "before";
81
+ };
69
82
  /** 左侧(旧/参照)值 */
70
83
  const leftValue = computed(() => {
71
84
  if (!payload.value) return {};
@@ -108,7 +121,7 @@ var HistoryDiffDialog_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
108
121
  });
109
122
  const open = (p) => {
110
123
  payload.value = p;
111
- mode.value = "before";
124
+ mode.value = resolveInitialMode(p.mode);
112
125
  viewMode.value = "form";
113
126
  visible.value = true;
114
127
  };
@@ -200,10 +213,13 @@ var HistoryDiffDialog_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
200
213
  size: "small",
201
214
  class: "m-editor-history-diff-dialog-mode"
202
215
  }, {
203
- default: withCtx(() => [createVNode(unref(TMagicRadioButton), { value: "before" }, {
216
+ default: withCtx(() => [createVNode(unref(TMagicRadioButton), {
217
+ value: "before",
218
+ disabled: !hasValue.value
219
+ }, {
204
220
  default: withCtx(() => [..._cache[7] || (_cache[7] = [createTextVNode("与修改前对比", -1)])]),
205
221
  _: 1
206
- }), createVNode(unref(TMagicRadioButton), {
222
+ }, 8, ["disabled"]), createVNode(unref(TMagicRadioButton), {
207
223
  value: "current",
208
224
  disabled: !hasCurrent.value
209
225
  }, {
@@ -150,12 +150,12 @@ var HistoryListPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
150
150
  /**
151
151
  * 「单步回滚」与「查看差异」的完整逻辑收敛到 useHistoryRevert,面板与业务方共用:
152
152
  * 二者均由 useHistoryRevert 内部按需动态挂载 HistoryDiffDialog,
153
- * 业务方亦可直接 import useHistoryRevert(services) 调用,无需自行挂载任何弹窗。
153
+ * 业务方亦可直接 import useHistoryRevert(options, services) 调用,无需自行挂载任何弹窗。
154
154
  */
155
- const { onPageRevert, onDataSourceRevert, onCodeBlockRevert, onPageDiff, onDataSourceDiff, onCodeBlockDiff } = useHistoryRevert(services, {
155
+ const { onPageRevert, onDataSourceRevert, onCodeBlockRevert, onPageDiff, onDataSourceDiff, onCodeBlockDiff } = useHistoryRevert({
156
156
  extendState: extendFormState,
157
157
  getPropsPanelFormState
158
- });
158
+ }, services);
159
159
  /**
160
160
  * 把内存中(已清空对应类别后的)历史状态重新写回 IndexedDB,
161
161
  * 使本地持久化的那份与内存保持一致——即「连同本地保存的一并删除」。
@@ -47,6 +47,7 @@ var mountHistoryDiffDialog = async (options) => {
47
47
  loadConfig: options.loadConfig,
48
48
  selfDiffFieldTypes: options.selfDiffFieldTypes,
49
49
  compareFormState: options.compareFormState,
50
+ width: options.width,
50
51
  onClose: options.onClose
51
52
  });
52
53
  if (options.appContext) Object.assign(app._context, options.appContext);
@@ -118,15 +119,14 @@ var viewHistoryDiffDialog = async (payload, options) => {
118
119
  * ```ts
119
120
  * import { useHistoryRevert } from '@tmagic/editor';
120
121
  *
121
- * const { onPageRevert, onPageDiff } = useHistoryRevert(editorRef.value); // editorRef.value 即 Editor 暴露的 services
122
+ * const { onPageRevert, onPageDiff } = useHistoryRevert({}, editorRef.value); // editorRef.value 即 Editor 暴露的 services
122
123
  * await onPageRevert(index); // 弹出差异 / 二次确认弹窗后回滚
123
124
  * onPageDiff(index); // 弹出只读差异弹窗查看前后值差异
124
125
  * ```
125
126
  */
126
- var useHistoryRevert = (services, options = {}) => {
127
- const { editorService, dataSourceService, codeBlockService, historyService } = services;
127
+ var useHistoryRevert = (options = {}, services) => {
128
128
  const appContext = options.appContext ?? getCurrentInstance()?.appContext ?? null;
129
- const { extendState, getPropsPanelFormState } = options;
129
+ const { extendState, getPropsPanelFormState, dialogWidth } = options;
130
130
  /** 目标数据已被删除、无法回滚时的统一提示。 */
131
131
  const showRevertTargetMissing = () => {
132
132
  tMagicMessage.error("回滚失败:该记录对应的数据已被删除");
@@ -147,28 +147,29 @@ var useHistoryRevert = (services, options = {}) => {
147
147
  appContext,
148
148
  extendState,
149
149
  services,
150
- ...extra
150
+ ...extra,
151
+ width: extra?.width ?? dialogWidth
151
152
  });
152
153
  return confirmRevert();
153
154
  };
154
155
  const buildPageDiffPayload = (index) => buildDiffPayload({
155
156
  category: "node",
156
- groups: () => historyService.getHistoryGroups("page", editorService.get("page")?.id),
157
- getCurrent: (id) => editorService.getNodeById(id),
157
+ groups: () => services?.historyService.getHistoryGroups("page", services?.editorService.get("page")?.id) ?? [],
158
+ getCurrent: (id) => services?.editorService.getNodeById(id),
158
159
  resolveType: (n, o) => n.type || o.type || "",
159
160
  resolveLabel: (n, o) => n.name || o.name || n.type || o.type || ""
160
161
  }, index);
161
162
  const buildDataSourceDiffPayload = (id, index) => buildDiffPayload({
162
163
  category: "data-source",
163
- groups: () => historyService.getHistoryGroups("dataSource"),
164
- getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`),
164
+ groups: () => services?.historyService.getHistoryGroups("dataSource") ?? [],
165
+ getCurrent: (id) => services?.dataSourceService.getDataSourceById(`${id}`),
165
166
  resolveType: (n, o) => n.type || o.type || "base",
166
167
  resolveLabel: (n, o, id) => n.title || o.title || `${id}`
167
168
  }, index, id);
168
169
  const buildCodeBlockDiffPayload = (id, index) => buildDiffPayload({
169
170
  category: "code-block",
170
- groups: () => historyService.getHistoryGroups("codeBlock"),
171
- getCurrent: (id) => codeBlockService.getCodeContentById(id),
171
+ groups: () => services?.historyService.getHistoryGroups("codeBlock") ?? [],
172
+ getCurrent: (id) => services?.codeBlockService.getCodeContentById(id),
172
173
  resolveLabel: (n, o, id) => n.name || o.name || `${id}`
173
174
  }, index, id);
174
175
  /**
@@ -178,45 +179,45 @@ var useHistoryRevert = (services, options = {}) => {
178
179
  * add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。
179
180
  */
180
181
  const isPageRevertTargetMissing = (index) => {
181
- const step = historyService.getStepList("page", editorService.get("page")?.id)[index]?.step;
182
+ const step = services?.historyService.getStepList("page", services?.editorService.get("page")?.id)[index]?.step;
182
183
  if (!step) return false;
183
184
  if (step.opType === "update") return (step.diff ?? []).some((item) => {
184
185
  const id = item.newSchema?.id ?? item.oldSchema?.id;
185
- return id !== void 0 && !editorService.getNodeById(id, false);
186
+ return id !== void 0 && !services?.editorService.getNodeById(id, false);
186
187
  });
187
- if (step.opType === "remove") return (step.diff ?? []).some((item) => item.parentId !== void 0 && !editorService.getNodeById(item.parentId, false));
188
+ if (step.opType === "remove") return (step.diff ?? []).some((item) => item.parentId !== void 0 && !services?.editorService.getNodeById(item.parentId, false));
188
189
  return false;
189
190
  };
190
191
  /** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */
191
192
  const isDataSourceRevertTargetMissing = (id, index) => {
192
- const step = historyService.getStepList("dataSource", id)[index]?.step;
193
- return Boolean(step?.opType === "update" && !dataSourceService.getDataSourceById(`${id}`));
193
+ const step = services?.historyService.getStepList("dataSource", id)[index]?.step;
194
+ return Boolean(step?.opType === "update" && !services?.dataSourceService.getDataSourceById(`${id}`));
194
195
  };
195
196
  /** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */
196
197
  const isCodeBlockRevertTargetMissing = (id, index) => {
197
- const step = historyService.getStepList("codeBlock", id)[index]?.step;
198
- return Boolean(step?.opType === "update" && !codeBlockService.getCodeContentById(id));
198
+ const step = services?.historyService.getStepList("codeBlock", id)[index]?.step;
199
+ return Boolean(step?.opType === "update" && !services?.codeBlockService.getCodeContentById(id));
199
200
  };
200
201
  const onPageRevert = (index) => {
201
202
  if (isPageRevertTargetMissing(index)) {
202
203
  showRevertTargetMissing();
203
204
  return Promise.resolve(null);
204
205
  }
205
- return runRevert(buildPageDiffPayload(index), { compareFormState: getPropsPanelFormState?.() }).then((result) => result ? editorService.revertPageStep(index) : null);
206
+ return runRevert(buildPageDiffPayload(index), { compareFormState: getPropsPanelFormState?.() }).then((result) => result ? services?.editorService.revertPageStep(index) : null);
206
207
  };
207
208
  const onDataSourceRevert = (id, index) => {
208
209
  if (isDataSourceRevertTargetMissing(id, index)) {
209
210
  showRevertTargetMissing();
210
211
  return Promise.resolve(null);
211
212
  }
212
- return runRevert(buildDataSourceDiffPayload(id, index)).then((result) => result ? dataSourceService.revert(id, index) : null);
213
+ return runRevert(buildDataSourceDiffPayload(id, index)).then((result) => result ? services?.dataSourceService.revert(id, index) : null);
213
214
  };
214
215
  const onCodeBlockRevert = (id, index) => {
215
216
  if (isCodeBlockRevertTargetMissing(id, index)) {
216
217
  showRevertTargetMissing();
217
218
  return Promise.resolve(null);
218
219
  }
219
- return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) => result ? codeBlockService.revert(id, index) : null);
220
+ return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) => result ? services?.codeBlockService.revert(id, index) : null);
220
221
  };
221
222
  /**
222
223
  * 「查看差异」:可差异对比的步骤(单实体 update)动态挂载只读 HistoryDiffDialog 展示前后值差异,
@@ -228,6 +229,7 @@ var useHistoryRevert = (services, options = {}) => {
228
229
  appContext,
229
230
  extendState,
230
231
  services,
232
+ width: dialogWidth,
231
233
  compareFormState: getPropsPanelFormState?.()
232
234
  });
233
235
  };
@@ -236,7 +238,8 @@ var useHistoryRevert = (services, options = {}) => {
236
238
  if (payload) return viewHistoryDiffDialog(payload, {
237
239
  appContext,
238
240
  extendState,
239
- services
241
+ services,
242
+ width: dialogWidth
240
243
  });
241
244
  };
242
245
  const onCodeBlockDiff = (id, index) => {
@@ -244,7 +247,8 @@ var useHistoryRevert = (services, options = {}) => {
244
247
  if (payload) return viewHistoryDiffDialog(payload, {
245
248
  appContext,
246
249
  extendState,
247
- services
250
+ services,
251
+ width: dialogWidth
248
252
  });
249
253
  };
250
254
  /**
@@ -270,7 +274,8 @@ var useHistoryRevert = (services, options = {}) => {
270
274
  }
271
275
  if (!await runRevert(revertOptions.diffPayload ?? null, {
272
276
  loadConfig: revertOptions.loadConfig,
273
- selfDiffFieldTypes: revertOptions.selfDiffFieldTypes
277
+ selfDiffFieldTypes: revertOptions.selfDiffFieldTypes,
278
+ width: revertOptions.width
274
279
  })) return null;
275
280
  return await revertOptions.revert();
276
281
  };
@@ -283,7 +288,8 @@ var useHistoryRevert = (services, options = {}) => {
283
288
  appContext,
284
289
  extendState,
285
290
  services,
286
- ...extra
291
+ ...extra,
292
+ width: extra?.width ?? dialogWidth
287
293
  });
288
294
  };
289
295
  return {
@@ -56,6 +56,7 @@ var CodeBlock = class extends BaseService {
56
56
  async setCodeDsl(codeDsl) {
57
57
  this.state.codeDsl = codeDsl;
58
58
  this.emit("code-dsl-change", this.state.codeDsl);
59
+ this.emit("change", this.state.codeDsl);
59
60
  }
60
61
  /**
61
62
  * 获取活动的代码块dsl数据源(默认从dsl中的codeBlocks字段读取)
@@ -132,6 +133,7 @@ var CodeBlock = class extends BaseService {
132
133
  this.lastPushedHistoryId = (step ? history_default.push("codeBlock", step, id) : null)?.uuid ?? null;
133
134
  }
134
135
  this.emit("addOrUpdate", id, codeDsl[id]);
136
+ this.emit("change", this.getCodeDsl());
135
137
  }
136
138
  /**
137
139
  * 根据代码块id数组获取代码dsl
@@ -229,6 +231,7 @@ var CodeBlock = class extends BaseService {
229
231
  }
230
232
  this.emit("remove", id);
231
233
  });
234
+ this.emit("change", this.getCodeDsl());
232
235
  }
233
236
  /**
234
237
  * 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
@@ -102,6 +102,7 @@ var DataSource = class extends BaseService {
102
102
  this.lastPushedHistoryId = (step ? history_default.push("dataSource", step, newConfig.id) : null)?.uuid ?? null;
103
103
  }
104
104
  this.emit("add", newConfig);
105
+ this.emit("change", this.get("dataSources"));
105
106
  return newConfig;
106
107
  }
107
108
  /**
@@ -132,6 +133,7 @@ var DataSource = class extends BaseService {
132
133
  oldConfig,
133
134
  changeRecords
134
135
  });
136
+ this.emit("change", this.get("dataSources"));
135
137
  return newConfig;
136
138
  }
137
139
  /**
@@ -156,6 +158,7 @@ var DataSource = class extends BaseService {
156
158
  this.lastPushedHistoryId = (step ? history_default.push("dataSource", step, id) : null)?.uuid ?? null;
157
159
  }
158
160
  this.emit("remove", id);
161
+ this.emit("change", this.get("dataSources"));
159
162
  }
160
163
  /**
161
164
  * 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
@@ -335,6 +335,13 @@ var Editor = class extends BaseService {
335
335
  } else this.selectionBeforeOp = null;
336
336
  }
337
337
  this.emit("add", newNodes);
338
+ this.emit("change", {
339
+ type: "add",
340
+ data: newNodes.map((node) => ({
341
+ node,
342
+ page: this.getPageOfNode(node.id)
343
+ }))
344
+ });
338
345
  return Array.isArray(addNode) ? newNodes : newNodes[0];
339
346
  }
340
347
  async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
@@ -398,6 +405,10 @@ var Editor = class extends BaseService {
398
405
  async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
399
406
  this.captureSelectionBeforeOp();
400
407
  const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
408
+ const changeItems = nodes.map((node) => ({
409
+ node,
410
+ page: this.getPageOfNode(node.id)
411
+ }));
401
412
  const removedItems = [];
402
413
  let pageForOp = null;
403
414
  if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) for (const n of nodes) {
@@ -427,6 +438,10 @@ var Editor = class extends BaseService {
427
438
  });
428
439
  else this.selectionBeforeOp = null;
429
440
  this.emit("remove", nodes);
441
+ this.emit("change", {
442
+ type: "remove",
443
+ data: changeItems
444
+ });
430
445
  }
431
446
  async doUpdate(config, { changeRecords = [], historySource } = {}) {
432
447
  if (!this.get("root")) throw new Error("root为空");
@@ -512,6 +527,13 @@ var Editor = class extends BaseService {
512
527
  } else this.selectionBeforeOp = null;
513
528
  }
514
529
  this.emit("update", updateData);
530
+ this.emit("change", {
531
+ type: "update",
532
+ data: updateData.map((node) => ({
533
+ node,
534
+ page: this.getPageOfNode(node.newNode.id)
535
+ }))
536
+ });
515
537
  return Array.isArray(config) ? updateData.map((item) => item.newNode) : updateData[0].newNode;
516
538
  }
517
539
  /**
@@ -677,6 +699,14 @@ var Editor = class extends BaseService {
677
699
  });
678
700
  } else this.selectionBeforeOp = null;
679
701
  this.emit("move-layer", offset);
702
+ this.emit("change", {
703
+ type: "move-layer",
704
+ data: [{
705
+ node,
706
+ page: this.getPageOfNode(node.id)
707
+ }],
708
+ offset
709
+ });
680
710
  }
681
711
  /**
682
712
  * 移动一个或多个节点到指定容器中。
@@ -806,6 +836,15 @@ var Editor = class extends BaseService {
806
836
  configs,
807
837
  targetParent
808
838
  });
839
+ this.emit("change", {
840
+ type: "drag-to",
841
+ data: configs.map((node) => ({
842
+ node,
843
+ page: this.getPageOfNode(node.id)
844
+ })),
845
+ targetIndex,
846
+ targetParent
847
+ });
809
848
  }
810
849
  /**
811
850
  * 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
@@ -1064,6 +1103,19 @@ var Editor = class extends BaseService {
1064
1103
  addModifiedNodeId(id) {
1065
1104
  this.get("modifiedNodeIds").set(id, id);
1066
1105
  }
1106
+ /**
1107
+ * 获取指定节点所属的页面 / 页面片:
1108
+ * - 普通节点返回其所在的 page;
1109
+ * - 节点本身就是 page / pageFragment 时返回它自己;
1110
+ * - 找不到时返回 null。
1111
+ * 供 `change` 事件携带「变更节点对应的 page」(而非编辑器当前选中页)。
1112
+ */
1113
+ getPageOfNode(id) {
1114
+ const { node, page } = this.getNodeInfo(id, false);
1115
+ if (page) return page;
1116
+ if (node && (isPage(node) || isPageFragment(node))) return node;
1117
+ return null;
1118
+ }
1067
1119
  captureSelectionBeforeOp() {
1068
1120
  if (this.selectionBeforeOp) return;
1069
1121
  this.selectionBeforeOp = this.get("nodes").map((n) => n.id);