@tmagic/editor 1.8.0-beta.4 → 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.
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/es/index.js +6 -3
- package/dist/es/initService.js +1 -1
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -55
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -39
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +149 -103
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +32 -8
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +333 -211
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -60
- package/dist/es/layouts/history-list/composables.js +73 -32
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/services/codeBlock.js +85 -37
- package/dist/es/services/dataSource.js +62 -26
- package/dist/es/services/editor.js +243 -100
- package/dist/es/services/history.js +270 -206
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +49 -6
- package/dist/es/utils/editor.js +35 -1
- package/dist/es/utils/history.js +223 -0
- package/dist/es/utils/indexed-db.js +86 -0
- package/dist/es/utils/undo-redo.js +60 -1
- package/dist/style.css +49 -6
- package/dist/tmagic-editor.umd.cjs +1799 -905
- package/package.json +7 -7
- package/src/components/CompareForm.vue +3 -1
- package/src/components/ToolButton.vue +2 -2
- package/src/index.ts +1 -0
- package/src/initService.ts +1 -1
- package/src/layouts/Framework.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +34 -71
- package/src/layouts/history-list/BucketTab.vue +46 -54
- package/src/layouts/history-list/GroupRow.vue +146 -111
- package/src/layouts/history-list/HistoryDiffDialog.vue +94 -68
- package/src/layouts/history-list/HistoryListPanel.vue +296 -113
- package/src/layouts/history-list/InitialRow.vue +25 -9
- package/src/layouts/history-list/PageTab.vue +57 -51
- package/src/layouts/history-list/composables.ts +157 -36
- package/src/layouts/page-bar/PageBar.vue +4 -2
- package/src/layouts/sidebar/Sidebar.vue +6 -1
- package/src/services/codeBlock.ts +107 -37
- package/src/services/dataSource.ts +89 -40
- package/src/services/editor.ts +370 -134
- package/src/services/history.ts +305 -203
- package/src/services/ui.ts +7 -0
- package/src/theme/history-list-panel.scss +72 -5
- package/src/theme/page-bar.scss +0 -4
- package/src/type.ts +167 -63
- package/src/utils/editor.ts +41 -1
- package/src/utils/history.ts +298 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/indexed-db.ts +122 -0
- package/src/utils/undo-redo.ts +88 -0
- package/types/index.d.ts +783 -291
package/src/services/editor.ts
CHANGED
|
@@ -17,13 +17,21 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { reactive, toRaw } from 'vue';
|
|
20
|
-
import { cloneDeep, isObject, mergeWith, uniq } from 'lodash-es';
|
|
20
|
+
import { cloneDeep, isEmpty, isEqual, isObject, mergeWith, uniq } from 'lodash-es';
|
|
21
21
|
|
|
22
22
|
import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
|
23
23
|
import { NodeType } from '@tmagic/core';
|
|
24
24
|
import type { ChangeRecord } from '@tmagic/form';
|
|
25
25
|
import { isFixed } from '@tmagic/stage';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
getNodeInfo,
|
|
28
|
+
getNodePath,
|
|
29
|
+
getValueByKeyPath,
|
|
30
|
+
guid,
|
|
31
|
+
isPage,
|
|
32
|
+
isPageFragment,
|
|
33
|
+
setValueByKeyPath,
|
|
34
|
+
} from '@tmagic/utils';
|
|
27
35
|
|
|
28
36
|
import BaseService from '@editor/services//BaseService';
|
|
29
37
|
import propsService from '@editor/services//props';
|
|
@@ -39,6 +47,7 @@ import type {
|
|
|
39
47
|
HistoryOpSource,
|
|
40
48
|
HistoryOpType,
|
|
41
49
|
PastePosition,
|
|
50
|
+
StepDiffItem,
|
|
42
51
|
StepValue,
|
|
43
52
|
StoreState,
|
|
44
53
|
StoreStateKey,
|
|
@@ -51,6 +60,7 @@ import {
|
|
|
51
60
|
classifyDragSources,
|
|
52
61
|
collectRelatedNodes,
|
|
53
62
|
COPY_STORAGE_KEY,
|
|
63
|
+
describeStepForRevert,
|
|
54
64
|
editorNodeMergeCustomizer,
|
|
55
65
|
fixNodePosition,
|
|
56
66
|
getInitPositionStyle,
|
|
@@ -68,36 +78,18 @@ import { beforePaste, getAddParent } from '@editor/utils/operator';
|
|
|
68
78
|
type MoveItem = { node: MNode; parent: MContainer; pageForOp: { name: string; id: Id } | null };
|
|
69
79
|
|
|
70
80
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
81
|
+
* 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
|
|
82
|
+
* `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
|
|
83
|
+
* 缺省(未传 / 空数组)才退化为整节点替换。
|
|
73
84
|
*/
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
case 'remove': {
|
|
83
|
-
const count = step.removedItems?.length ?? 0;
|
|
84
|
-
const node = step.removedItems?.[0]?.node;
|
|
85
|
-
const label = node?.name || node?.type || (node?.id !== undefined ? `${node.id}` : '');
|
|
86
|
-
return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ''}`;
|
|
87
|
-
}
|
|
88
|
-
case 'update':
|
|
89
|
-
default: {
|
|
90
|
-
const items = step.updatedItems ?? [];
|
|
91
|
-
if (items.length === 1) {
|
|
92
|
-
const { newNode, oldNode, changeRecords } = items[0];
|
|
93
|
-
const target = newNode?.name || newNode?.type || oldNode?.name || oldNode?.type || `${newNode?.id ?? ''}`;
|
|
94
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
95
|
-
return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
|
|
96
|
-
}
|
|
97
|
-
return `还原 ${items.length} 个节点的修改`;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
85
|
+
const buildUpdateDiff = (
|
|
86
|
+
items: { oldNode: MNode; newNode: MNode; changeRecords?: ChangeRecord[] }[],
|
|
87
|
+
): StepDiffItem<MNode>[] =>
|
|
88
|
+
items.map(({ oldNode, newNode, changeRecords }) => ({
|
|
89
|
+
oldSchema: oldNode,
|
|
90
|
+
newSchema: newNode,
|
|
91
|
+
...(changeRecords?.length ? { changeRecords } : {}),
|
|
92
|
+
}));
|
|
101
93
|
|
|
102
94
|
class Editor extends BaseService {
|
|
103
95
|
public state: StoreState = reactive({
|
|
@@ -116,6 +108,12 @@ class Editor extends BaseService {
|
|
|
116
108
|
alwaysMultiSelect: false,
|
|
117
109
|
});
|
|
118
110
|
private selectionBeforeOp: Id[] | null = null;
|
|
111
|
+
/**
|
|
112
|
+
* 最近一次 pushOpHistory 写入的历史记录 uuid。
|
|
113
|
+
* 供 *AndGetHistoryId 系列方法在调用普通操作后取回本次产生的历史记录 id;
|
|
114
|
+
* 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
|
|
115
|
+
*/
|
|
116
|
+
private lastPushedHistoryId: string | null = null;
|
|
119
117
|
|
|
120
118
|
constructor() {
|
|
121
119
|
super(
|
|
@@ -129,8 +127,13 @@ class Editor extends BaseService {
|
|
|
129
127
|
* 设置当前指点节点配置
|
|
130
128
|
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
|
|
131
129
|
* @param value MNode
|
|
130
|
+
* @param options.historySource 设置 root 时,本次变更写入历史记录的「操作来源」(仅 name === 'root' 时生效)
|
|
132
131
|
*/
|
|
133
|
-
public set<K extends StoreStateKey, T extends StoreState[K]>(
|
|
132
|
+
public set<K extends StoreStateKey, T extends StoreState[K]>(
|
|
133
|
+
name: K,
|
|
134
|
+
value: T,
|
|
135
|
+
options: { historySource?: HistoryOpSource } = {},
|
|
136
|
+
) {
|
|
134
137
|
const preValue = this.state[name];
|
|
135
138
|
this.state[name] = value;
|
|
136
139
|
|
|
@@ -149,6 +152,25 @@ class Editor extends BaseService {
|
|
|
149
152
|
this.state.pageLength = getPageList(app).length || 0;
|
|
150
153
|
this.state.pageFragmentLength = getPageFragmentList(app).length || 0;
|
|
151
154
|
this.state.stageLoading = this.state.pageLength !== 0;
|
|
155
|
+
|
|
156
|
+
if (preValue && !isEmpty(preValue)) {
|
|
157
|
+
// 编辑期间再次整体设置 root(源码保存 / 外部重设 DSL / root 节点更新):与上一次 root
|
|
158
|
+
// 做页面级 diff,按 update / add / remove 入栈,作为正常历史记录体现整体替换。
|
|
159
|
+
this.pushRootDiffHistory(preValue as MApp, app, options.historySource);
|
|
160
|
+
} else {
|
|
161
|
+
// 首次设置 root:仅当该页面 / 页面片尚无基线标记时,才写入「未修改的初始状态」基线。
|
|
162
|
+
// 配合「先恢复历史再 set root」:若基线已随历史恢复建立(恢复后已有基线),则此处不再
|
|
163
|
+
// 重复创建,set root 不额外产生记录,由恢复出的历史栈作为当前状态来源。
|
|
164
|
+
// 标记不进入撤销/重做栈,仅作为该页历史列表底部的初始基线展示。
|
|
165
|
+
app.items?.forEach((pageNode) => {
|
|
166
|
+
if (pageNode?.id !== undefined && !historyService.getPageMarker(pageNode.id)) {
|
|
167
|
+
historyService.setPageMarker(pageNode.id, {
|
|
168
|
+
name: pageNode.name,
|
|
169
|
+
source: options.historySource,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
152
174
|
} else {
|
|
153
175
|
this.state.pageLength = 0;
|
|
154
176
|
this.state.pageFragmentLength = 0;
|
|
@@ -180,7 +202,30 @@ class Editor extends BaseService {
|
|
|
180
202
|
root = toRaw(root);
|
|
181
203
|
}
|
|
182
204
|
|
|
183
|
-
|
|
205
|
+
if (!root) {
|
|
206
|
+
return { node: null, parent: null, page: null };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (id === root.id) {
|
|
210
|
+
return { node: root, parent: null, page: null };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 大多数查找的目标都在当前页面内,优先在当前页面子树中查找以避免对整棵树做全量遍历。
|
|
214
|
+
// 注意:不能直接使用 state.page,它可能与当前 root 不同步(指向已脱离的旧页面对象),
|
|
215
|
+
// 因此仅借用其 id,再从当前 root 中取回真正的页面对象(页面均为 root 的直接子节点,数量很少)。
|
|
216
|
+
const pageIdStr = `${this.get('page')?.id || ''}`;
|
|
217
|
+
const currentPageNode = root.items?.find((item) => `${item.id}` === pageIdStr);
|
|
218
|
+
if (currentPageNode && `${id}` !== pageIdStr) {
|
|
219
|
+
// util 仅读取 root.id 与 root.items,按容器结构传入当前页面是安全的
|
|
220
|
+
const info = getNodeInfo(id, currentPageNode);
|
|
221
|
+
if (info.node) {
|
|
222
|
+
return info;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 回退:在完整 root 上查找;当前页面已搜索过,用 skip 跳过其子树避免重复遍历,
|
|
227
|
+
// 同时保留真实的 parent / page 引用(id 命中当前页面节点本身时会在跳过子树前先匹配到)
|
|
228
|
+
return getNodeInfo(id, root, currentPageNode);
|
|
184
229
|
}
|
|
185
230
|
|
|
186
231
|
/**
|
|
@@ -473,17 +518,17 @@ class Editor extends BaseService {
|
|
|
473
518
|
if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
|
|
474
519
|
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
|
475
520
|
if (!doNotPushHistory) {
|
|
521
|
+
const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get('root'))!.id;
|
|
476
522
|
this.pushOpHistory('add', {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
},
|
|
523
|
+
diff: newNodes.map((n) => {
|
|
524
|
+
const p = this.getParentById(n.id, false) as MContainer;
|
|
525
|
+
const idx = p ? getNodeIndex(n.id, p) : -1;
|
|
526
|
+
return {
|
|
527
|
+
newSchema: cloneDeep(toRaw(n)),
|
|
528
|
+
parentId,
|
|
529
|
+
index: typeof idx === 'number' ? idx : -1,
|
|
530
|
+
};
|
|
531
|
+
}),
|
|
487
532
|
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
488
533
|
historyDescription,
|
|
489
534
|
source: historySource,
|
|
@@ -596,7 +641,7 @@ class Editor extends BaseService {
|
|
|
596
641
|
|
|
597
642
|
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
|
598
643
|
|
|
599
|
-
const removedItems:
|
|
644
|
+
const removedItems: StepDiffItem<MNode>[] = [];
|
|
600
645
|
let pageForOp: { name: string; id: Id } | null = null;
|
|
601
646
|
if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) {
|
|
602
647
|
for (const n of nodes) {
|
|
@@ -607,7 +652,7 @@ class Editor extends BaseService {
|
|
|
607
652
|
}
|
|
608
653
|
const idx = getNodeIndex(curNode.id, parent);
|
|
609
654
|
removedItems.push({
|
|
610
|
-
|
|
655
|
+
oldSchema: cloneDeep(toRaw(curNode)),
|
|
611
656
|
parentId: parent.id,
|
|
612
657
|
index: typeof idx === 'number' ? idx : -1,
|
|
613
658
|
});
|
|
@@ -620,7 +665,7 @@ class Editor extends BaseService {
|
|
|
620
665
|
if (removedItems.length > 0 && pageForOp) {
|
|
621
666
|
if (!doNotPushHistory) {
|
|
622
667
|
this.pushOpHistory('remove', {
|
|
623
|
-
|
|
668
|
+
diff: removedItems,
|
|
624
669
|
pageData: pageForOp,
|
|
625
670
|
historyDescription,
|
|
626
671
|
source: historySource,
|
|
@@ -635,7 +680,7 @@ class Editor extends BaseService {
|
|
|
635
680
|
|
|
636
681
|
public async doUpdate(
|
|
637
682
|
config: MNode,
|
|
638
|
-
{ changeRecords = [] }: { changeRecords?: ChangeRecord[] } = {},
|
|
683
|
+
{ changeRecords = [], historySource }: { changeRecords?: ChangeRecord[]; historySource?: HistoryOpSource } = {},
|
|
639
684
|
): Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }> {
|
|
640
685
|
const root = this.get('root');
|
|
641
686
|
if (!root) throw new Error('root为空');
|
|
@@ -655,7 +700,7 @@ class Editor extends BaseService {
|
|
|
655
700
|
if (!newConfig.type) throw new Error('配置缺少type值');
|
|
656
701
|
|
|
657
702
|
if (newConfig.type === NodeType.ROOT) {
|
|
658
|
-
this.set('root', newConfig as MApp);
|
|
703
|
+
this.set('root', newConfig as MApp, { historySource });
|
|
659
704
|
return {
|
|
660
705
|
oldNode: node,
|
|
661
706
|
newNode: newConfig,
|
|
@@ -736,7 +781,7 @@ class Editor extends BaseService {
|
|
|
736
781
|
const updateData = await Promise.all(
|
|
737
782
|
nodes.map((node, index) => {
|
|
738
783
|
const recordsForNode = changeRecordList ? (changeRecordList[index] ?? []) : (changeRecords ?? []);
|
|
739
|
-
return this.doUpdate(node, { changeRecords: recordsForNode });
|
|
784
|
+
return this.doUpdate(node, { changeRecords: recordsForNode, historySource });
|
|
740
785
|
}),
|
|
741
786
|
);
|
|
742
787
|
|
|
@@ -746,15 +791,15 @@ class Editor extends BaseService {
|
|
|
746
791
|
if (!doNotPushHistory) {
|
|
747
792
|
const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
|
|
748
793
|
this.pushOpHistory('update', {
|
|
749
|
-
|
|
750
|
-
|
|
794
|
+
// 每个节点单独保留自己的 changeRecords,便于撤销/重做时按 propPath 精细化更新;
|
|
795
|
+
// 没有 changeRecords 的(如内部 sort/moveLayer 等整节点替换操作)会退化为全节点替换。
|
|
796
|
+
diff: buildUpdateDiff(
|
|
797
|
+
updateData.map((d) => ({
|
|
751
798
|
oldNode: cloneDeep(d.oldNode),
|
|
752
|
-
newNode: cloneDeep(
|
|
753
|
-
// 每个节点单独保留自己的 changeRecords,便于撤销/重做时按 propPath 精细化更新;
|
|
754
|
-
// 没有 changeRecords 的(如内部 sort/moveLayer 等整节点替换操作)会退化为全节点替换。
|
|
799
|
+
newNode: cloneDeep(d.newNode),
|
|
755
800
|
changeRecords: d.changeRecords?.length ? cloneDeep(d.changeRecords) : undefined,
|
|
756
801
|
})),
|
|
757
|
-
|
|
802
|
+
),
|
|
758
803
|
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
759
804
|
historyDescription,
|
|
760
805
|
source: historySource,
|
|
@@ -996,9 +1041,7 @@ class Editor extends BaseService {
|
|
|
996
1041
|
'update',
|
|
997
1042
|
|
|
998
1043
|
{
|
|
999
|
-
|
|
1000
|
-
updatedItems: [{ oldNode: oldParent, newNode: cloneDeep(toRaw(parent)) }],
|
|
1001
|
-
},
|
|
1044
|
+
diff: buildUpdateDiff([{ oldNode: oldParent, newNode: cloneDeep(toRaw(parent)) }]),
|
|
1002
1045
|
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
1003
1046
|
historyDescription,
|
|
1004
1047
|
source: historySource,
|
|
@@ -1098,7 +1141,7 @@ class Editor extends BaseService {
|
|
|
1098
1141
|
}));
|
|
1099
1142
|
const historyPage = moves[0].pageForOp ?? { name: '', id: target.id };
|
|
1100
1143
|
this.pushOpHistory('update', {
|
|
1101
|
-
|
|
1144
|
+
diff: buildUpdateDiff(updatedItems),
|
|
1102
1145
|
pageData: historyPage,
|
|
1103
1146
|
historyDescription,
|
|
1104
1147
|
source: historySource,
|
|
@@ -1178,7 +1221,7 @@ class Editor extends BaseService {
|
|
|
1178
1221
|
if (!doNotPushHistory) {
|
|
1179
1222
|
const pageForOp = this.getNodeInfo(configs[0].id, false).page;
|
|
1180
1223
|
this.pushOpHistory('update', {
|
|
1181
|
-
|
|
1224
|
+
diff: buildUpdateDiff(updatedItems),
|
|
1182
1225
|
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
1183
1226
|
historyDescription,
|
|
1184
1227
|
source: historySource,
|
|
@@ -1190,6 +1233,86 @@ class Editor extends BaseService {
|
|
|
1190
1233
|
this.emit('drag-to', { targetIndex, configs, targetParent });
|
|
1191
1234
|
}
|
|
1192
1235
|
|
|
1236
|
+
// #region AndGetHistoryId
|
|
1237
|
+
/**
|
|
1238
|
+
* 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
|
|
1239
|
+
* 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link StepValue.uuid}),
|
|
1240
|
+
* 而非节点 / 节点数组。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
1241
|
+
*
|
|
1242
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时返回 null。
|
|
1243
|
+
*/
|
|
1244
|
+
|
|
1245
|
+
/** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1246
|
+
public async addAndGetHistoryId(
|
|
1247
|
+
addNode: AddMNode | MNode[],
|
|
1248
|
+
parent?: MContainer | null,
|
|
1249
|
+
options: DslOpOptions = {},
|
|
1250
|
+
): Promise<string | null> {
|
|
1251
|
+
this.lastPushedHistoryId = null;
|
|
1252
|
+
await this.add(addNode, parent, options);
|
|
1253
|
+
return this.lastPushedHistoryId;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1257
|
+
public async removeAndGetHistoryId(
|
|
1258
|
+
nodeOrNodeList: MNode | MNode[],
|
|
1259
|
+
options: DslOpOptions = {},
|
|
1260
|
+
): Promise<string | null> {
|
|
1261
|
+
this.lastPushedHistoryId = null;
|
|
1262
|
+
await this.remove(nodeOrNodeList, options);
|
|
1263
|
+
return this.lastPushedHistoryId;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1267
|
+
public async updateAndGetHistoryId(
|
|
1268
|
+
config: MNode | MNode[],
|
|
1269
|
+
data: {
|
|
1270
|
+
changeRecords?: ChangeRecord[];
|
|
1271
|
+
changeRecordList?: ChangeRecord[][];
|
|
1272
|
+
doNotPushHistory?: boolean;
|
|
1273
|
+
historyDescription?: string;
|
|
1274
|
+
historySource?: HistoryOpSource;
|
|
1275
|
+
} = {},
|
|
1276
|
+
): Promise<string | null> {
|
|
1277
|
+
this.lastPushedHistoryId = null;
|
|
1278
|
+
await this.update(config, data);
|
|
1279
|
+
return this.lastPushedHistoryId;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/** 等价于 {@link moveLayer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1283
|
+
public async moveLayerAndGetHistoryId(
|
|
1284
|
+
offset: number | LayerOffset,
|
|
1285
|
+
options: DslOpOptions = {},
|
|
1286
|
+
): Promise<string | null> {
|
|
1287
|
+
this.lastPushedHistoryId = null;
|
|
1288
|
+
await this.moveLayer(offset, options);
|
|
1289
|
+
return this.lastPushedHistoryId;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** 等价于 {@link moveToContainer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1293
|
+
public async moveToContainerAndGetHistoryId(
|
|
1294
|
+
config: MNode | MNode[],
|
|
1295
|
+
targetId: Id,
|
|
1296
|
+
options: DslOpOptions = {},
|
|
1297
|
+
): Promise<string | null> {
|
|
1298
|
+
this.lastPushedHistoryId = null;
|
|
1299
|
+
await this.moveToContainer(config, targetId, options);
|
|
1300
|
+
return this.lastPushedHistoryId;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/** 等价于 {@link dragTo},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
1304
|
+
public async dragToAndGetHistoryId(
|
|
1305
|
+
config: MNode | MNode[],
|
|
1306
|
+
targetParent: MContainer,
|
|
1307
|
+
targetIndex: number,
|
|
1308
|
+
options: DslOpOptions = {},
|
|
1309
|
+
): Promise<string | null> {
|
|
1310
|
+
this.lastPushedHistoryId = null;
|
|
1311
|
+
await this.dragTo(config, targetParent, targetIndex, options);
|
|
1312
|
+
return this.lastPushedHistoryId;
|
|
1313
|
+
}
|
|
1314
|
+
// #endregion AndGetHistoryId
|
|
1315
|
+
|
|
1193
1316
|
/**
|
|
1194
1317
|
* 撤销当前操作
|
|
1195
1318
|
* @returns 被撤销的操作
|
|
@@ -1236,12 +1359,14 @@ class Editor extends BaseService {
|
|
|
1236
1359
|
if (!entry?.applied) return null;
|
|
1237
1360
|
|
|
1238
1361
|
const { step } = entry;
|
|
1362
|
+
// 初始基线(index 0 的 initial step)是栈底线,不可回滚。
|
|
1363
|
+
if (step.opType === 'initial') return null;
|
|
1239
1364
|
const root = this.get('root');
|
|
1240
1365
|
if (!root) return null;
|
|
1241
1366
|
|
|
1242
1367
|
// 更新类步骤必须带 changeRecords 才支持回滚:缺失时只能整节点替换,会冲掉后续无关变更,故不支持。
|
|
1243
1368
|
if (step.opType === 'update') {
|
|
1244
|
-
const items = step.
|
|
1369
|
+
const items = step.diff ?? [];
|
|
1245
1370
|
if (!items.length || !items.every((item) => item.changeRecords?.length)) return null;
|
|
1246
1371
|
}
|
|
1247
1372
|
|
|
@@ -1260,9 +1385,9 @@ class Editor extends BaseService {
|
|
|
1260
1385
|
switch (step.opType) {
|
|
1261
1386
|
case 'add': {
|
|
1262
1387
|
// 原本是新增 → revert 即删除当时被加入的节点
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
const existing = this.getNodeById(
|
|
1388
|
+
for (const { newSchema } of step.diff ?? []) {
|
|
1389
|
+
if (!newSchema) continue;
|
|
1390
|
+
const existing = this.getNodeById(newSchema.id, false);
|
|
1266
1391
|
if (existing) {
|
|
1267
1392
|
await this.remove(existing, opts);
|
|
1268
1393
|
}
|
|
@@ -1272,35 +1397,40 @@ class Editor extends BaseService {
|
|
|
1272
1397
|
case 'remove': {
|
|
1273
1398
|
// 原本是删除 → revert 即把节点按原父容器加回来。
|
|
1274
1399
|
// 按原 index 升序逐个插回,先小后大避免索引漂移。
|
|
1275
|
-
const items = step.
|
|
1276
|
-
const sorted = [...items].sort((a, b) => a.index - b.index);
|
|
1277
|
-
for (const {
|
|
1400
|
+
const items = step.diff ?? [];
|
|
1401
|
+
const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1402
|
+
for (const { oldSchema, parentId } of sorted) {
|
|
1403
|
+
if (!oldSchema || parentId === undefined) continue;
|
|
1278
1404
|
const parent = this.getNodeById(parentId, false) as MContainer | null;
|
|
1279
1405
|
if (parent) {
|
|
1280
|
-
await this.add([cloneDeep(
|
|
1406
|
+
await this.add([cloneDeep(oldSchema)] as MNode[], parent, opts);
|
|
1281
1407
|
}
|
|
1282
1408
|
}
|
|
1283
1409
|
break;
|
|
1284
1410
|
}
|
|
1285
1411
|
case 'update': {
|
|
1286
|
-
// 原本是更新 → revert 即把
|
|
1412
|
+
// 原本是更新 → revert 即把 oldSchema 的值写回;
|
|
1287
1413
|
// 优先按 changeRecords 局部 patch(仅触达 propPath 下的字段,避免冲掉同节点上其它无关变更)。
|
|
1288
|
-
const items = step.
|
|
1289
|
-
const configs = items
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1414
|
+
const items = step.diff ?? [];
|
|
1415
|
+
const configs = items
|
|
1416
|
+
.filter((item) => item.oldSchema && item.newSchema)
|
|
1417
|
+
.map(({ oldSchema, newSchema, changeRecords }) => {
|
|
1418
|
+
const oldNode = oldSchema!;
|
|
1419
|
+
const newNode = newSchema!;
|
|
1420
|
+
if (changeRecords?.length) {
|
|
1421
|
+
const patch: MNode = { id: newNode.id, type: newNode.type };
|
|
1422
|
+
for (const record of changeRecords) {
|
|
1423
|
+
if (!record.propPath) {
|
|
1424
|
+
// 没有 propPath 视为整节点替换
|
|
1425
|
+
return cloneDeep(oldNode);
|
|
1426
|
+
}
|
|
1427
|
+
const value = cloneDeep(getValueByKeyPath(record.propPath, oldNode));
|
|
1428
|
+
setValueByKeyPath(record.propPath, value, patch);
|
|
1296
1429
|
}
|
|
1297
|
-
|
|
1298
|
-
setValueByKeyPath(record.propPath, value, patch);
|
|
1430
|
+
return patch;
|
|
1299
1431
|
}
|
|
1300
|
-
return
|
|
1301
|
-
}
|
|
1302
|
-
return cloneDeep(oldNode);
|
|
1303
|
-
});
|
|
1432
|
+
return cloneDeep(oldNode);
|
|
1433
|
+
});
|
|
1304
1434
|
if (configs.length) {
|
|
1305
1435
|
await this.update(configs, { historyDescription, historySource: 'rollback' });
|
|
1306
1436
|
}
|
|
@@ -1320,6 +1450,20 @@ class Editor extends BaseService {
|
|
|
1320
1450
|
return revertedStep;
|
|
1321
1451
|
}
|
|
1322
1452
|
|
|
1453
|
+
/**
|
|
1454
|
+
* 通过历史记录 uuid 回滚当前页面的某条历史步骤,语义与 {@link revertPageStep} 完全一致,
|
|
1455
|
+
* 仅入参从 index 改为 uuid({@link StepValue.uuid})。uuid 不随栈内步骤增删而变化,
|
|
1456
|
+
* 更适合业务侧持有引用后再回滚(埋点、跨端同步等场景)。
|
|
1457
|
+
*
|
|
1458
|
+
* @param uuid 目标历史记录的 uuid,通常由 *AndGetHistoryId 方法返回
|
|
1459
|
+
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用 / 反向失败时返回 null
|
|
1460
|
+
*/
|
|
1461
|
+
public async revertPageStepById(uuid: string): Promise<StepValue | null> {
|
|
1462
|
+
const index = historyService.getPageStepIndexByUuid(uuid);
|
|
1463
|
+
if (index < 0) return null;
|
|
1464
|
+
return this.revertPageStep(index);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1323
1467
|
/**
|
|
1324
1468
|
* 跳转当前页面历史栈到指定游标位置。
|
|
1325
1469
|
*
|
|
@@ -1416,34 +1560,119 @@ class Editor extends BaseService {
|
|
|
1416
1560
|
this.selectionBeforeOp = this.get('nodes').map((n) => n.id);
|
|
1417
1561
|
}
|
|
1418
1562
|
|
|
1563
|
+
/**
|
|
1564
|
+
* 比较「上一次 root」与「新 root」的页面 / 页面片,按页面粒度把整体替换拆成历史记录:
|
|
1565
|
+
* - 新旧都存在且内容变化的页面 → 一条 `update`(整页快照替换,无 changeRecords);
|
|
1566
|
+
* - 仅新 root 存在的页面 → 一条 `add`;
|
|
1567
|
+
* - 仅旧 root 存在的页面 → 一条 `remove`。
|
|
1568
|
+
*
|
|
1569
|
+
* 每条记录落到对应页面自己的历史栈(与普通节点操作一致),并标记来源 `source`。
|
|
1570
|
+
* 内容未变化的页面不产生记录,避免重复设置相同 DSL 时产生噪声。
|
|
1571
|
+
*/
|
|
1572
|
+
private pushRootDiffHistory(preRoot: MApp, nextRoot: MApp, source?: HistoryOpSource): void {
|
|
1573
|
+
const prevPages = preRoot?.items || [];
|
|
1574
|
+
const nextPages = nextRoot?.items || [];
|
|
1575
|
+
const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
|
|
1576
|
+
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
|
1577
|
+
const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
|
1578
|
+
|
|
1579
|
+
nextPages.forEach((nextPage) => {
|
|
1580
|
+
const prevPage = prevMap.get(`${nextPage.id}`);
|
|
1581
|
+
if (!prevPage) {
|
|
1582
|
+
this.pushPageDiffStep(
|
|
1583
|
+
'add',
|
|
1584
|
+
nextPage,
|
|
1585
|
+
{ newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
|
|
1586
|
+
source,
|
|
1587
|
+
);
|
|
1588
|
+
} else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
|
|
1589
|
+
this.pushPageDiffStep(
|
|
1590
|
+
'update',
|
|
1591
|
+
nextPage,
|
|
1592
|
+
{ oldSchema: cloneDeep(toRaw(prevPage)), newSchema: cloneDeep(toRaw(nextPage)) },
|
|
1593
|
+
source,
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
prevPages.forEach((prevPage) => {
|
|
1599
|
+
if (!nextMap.has(`${prevPage.id}`)) {
|
|
1600
|
+
this.pushPageDiffStep(
|
|
1601
|
+
'remove',
|
|
1602
|
+
prevPage,
|
|
1603
|
+
{ oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
|
|
1604
|
+
source,
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* 构造一条页面级「set root」历史记录(不携带选区 / modifiedNodeIds 上下文)并落到该页面自己的栈。
|
|
1612
|
+
*
|
|
1613
|
+
* 连续 set root 替换:若该页栈最新一条已是**同来源**的 set root 记录({@link StepValue.rootStep} 且 `source` 相同),
|
|
1614
|
+
* 则用本次记录**替换**它而非新增,避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录;
|
|
1615
|
+
* 来源不同则照常新增(initial 基线不是 rootStep,不在此列)。
|
|
1616
|
+
*/
|
|
1617
|
+
private pushPageDiffStep(
|
|
1618
|
+
opType: HistoryOpType,
|
|
1619
|
+
page: MPage | MPageFragment,
|
|
1620
|
+
diffItem: StepDiffItem<MNode>,
|
|
1621
|
+
source?: HistoryOpSource,
|
|
1622
|
+
): void {
|
|
1623
|
+
const step: StepValue = {
|
|
1624
|
+
uuid: guid(),
|
|
1625
|
+
data: { name: page.name || '', id: page.id },
|
|
1626
|
+
opType,
|
|
1627
|
+
selectedBefore: [],
|
|
1628
|
+
selectedAfter: [],
|
|
1629
|
+
modifiedNodeIds: new Map(),
|
|
1630
|
+
diff: [diffItem],
|
|
1631
|
+
rootStep: true,
|
|
1632
|
+
};
|
|
1633
|
+
if (source) step.source = source;
|
|
1634
|
+
|
|
1635
|
+
const top = historyService.getCurrentPageStep(page.id);
|
|
1636
|
+
if (top?.rootStep && top.source === source) {
|
|
1637
|
+
historyService.replaceCurrentPageStep(step, page.id);
|
|
1638
|
+
} else {
|
|
1639
|
+
historyService.push(step, page.id);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1419
1643
|
private pushOpHistory(
|
|
1420
1644
|
opType: HistoryOpType,
|
|
1421
1645
|
{
|
|
1422
|
-
|
|
1646
|
+
diff,
|
|
1423
1647
|
pageData,
|
|
1424
1648
|
historyDescription,
|
|
1425
1649
|
source,
|
|
1426
1650
|
}: {
|
|
1427
|
-
|
|
1651
|
+
diff: StepDiffItem<MNode>[];
|
|
1428
1652
|
pageData: { name: string; id: Id };
|
|
1429
1653
|
historyDescription?: string;
|
|
1430
1654
|
source?: HistoryOpSource;
|
|
1431
1655
|
},
|
|
1432
|
-
) {
|
|
1656
|
+
): string | null {
|
|
1433
1657
|
const step: StepValue = {
|
|
1658
|
+
uuid: guid(),
|
|
1434
1659
|
data: pageData,
|
|
1435
1660
|
opType,
|
|
1436
1661
|
selectedBefore: this.selectionBeforeOp ?? [],
|
|
1437
1662
|
selectedAfter: this.get('nodes').map((n) => n.id),
|
|
1438
1663
|
modifiedNodeIds: new Map(this.get('modifiedNodeIds')),
|
|
1439
|
-
|
|
1664
|
+
diff,
|
|
1440
1665
|
};
|
|
1441
1666
|
if (historyDescription) step.historyDescription = historyDescription;
|
|
1442
1667
|
if (source) step.source = source;
|
|
1443
1668
|
// 显式按 step.data.id 入栈:跨页操作(如 moveToContainer 从源页搬到目标页)
|
|
1444
1669
|
// 必须落到正确的页面栈,否则会把记录错误地推到当前活动页 / 操作发起页。
|
|
1445
|
-
historyService.push(step, pageData.id);
|
|
1670
|
+
const pushed = historyService.push(step, pageData.id);
|
|
1671
|
+
// push 返回 null 表示当前没有可写入的页面栈(未真正入栈),此时不应返回 uuid。
|
|
1672
|
+
const historyId = pushed ? step.uuid : null;
|
|
1673
|
+
this.lastPushedHistoryId = historyId;
|
|
1446
1674
|
this.selectionBeforeOp = null;
|
|
1675
|
+
return historyId;
|
|
1447
1676
|
}
|
|
1448
1677
|
|
|
1449
1678
|
/**
|
|
@@ -1459,6 +1688,8 @@ class Editor extends BaseService {
|
|
|
1459
1688
|
* @param reverse true = 撤销,false = 重做
|
|
1460
1689
|
*/
|
|
1461
1690
|
private async applyHistoryOp(step: StepValue, reverse: boolean) {
|
|
1691
|
+
// 初始基线 step 仅作展示,不承载任何变更,撤销/重做时无需应用(正常流程下也不会被触达)。
|
|
1692
|
+
if (step.opType === 'initial') return;
|
|
1462
1693
|
const root = this.get('root');
|
|
1463
1694
|
const stage = this.get('stage');
|
|
1464
1695
|
if (!root) return;
|
|
@@ -1467,52 +1698,52 @@ class Editor extends BaseService {
|
|
|
1467
1698
|
|
|
1468
1699
|
switch (step.opType) {
|
|
1469
1700
|
case 'add': {
|
|
1470
|
-
const
|
|
1701
|
+
const items = step.diff ?? [];
|
|
1471
1702
|
if (reverse) {
|
|
1472
1703
|
// 撤销 add:把当时加入的节点删除
|
|
1473
|
-
for (const
|
|
1474
|
-
|
|
1704
|
+
for (const { newSchema } of items) {
|
|
1705
|
+
if (!newSchema) continue;
|
|
1706
|
+
const existing = this.getNodeById(newSchema.id, false);
|
|
1475
1707
|
if (existing) {
|
|
1476
1708
|
await this.remove(existing, commonOpts);
|
|
1477
1709
|
}
|
|
1478
1710
|
}
|
|
1479
1711
|
} else {
|
|
1480
|
-
// 重做 add:按记录的
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
if (parent.items) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
parent.items.push(cloneDeep(n));
|
|
1492
|
-
}
|
|
1493
|
-
await stage?.add({
|
|
1494
|
-
config: cloneDeep(n),
|
|
1495
|
-
parent: cloneDeep(parent),
|
|
1496
|
-
parentId: parent.id,
|
|
1497
|
-
root: cloneDeep(root),
|
|
1498
|
-
});
|
|
1712
|
+
// 重做 add:按记录的 parentId / index 把节点重新插回父容器。
|
|
1713
|
+
// 按目标 index 升序逐个插入,先小后大避免索引漂移
|
|
1714
|
+
const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1715
|
+
for (const { newSchema, parentId, index } of sorted) {
|
|
1716
|
+
if (!newSchema || parentId === undefined) continue;
|
|
1717
|
+
const parent = this.getNodeById(parentId, false) as MContainer | null;
|
|
1718
|
+
if (parent?.items) {
|
|
1719
|
+
if (typeof index === 'number' && index >= 0 && index < parent.items.length) {
|
|
1720
|
+
parent.items.splice(index, 0, cloneDeep(newSchema));
|
|
1721
|
+
} else {
|
|
1722
|
+
parent.items.push(cloneDeep(newSchema));
|
|
1499
1723
|
}
|
|
1724
|
+
await stage?.add({
|
|
1725
|
+
config: cloneDeep(newSchema),
|
|
1726
|
+
parent: cloneDeep(parent),
|
|
1727
|
+
parentId: parent.id,
|
|
1728
|
+
root: cloneDeep(root),
|
|
1729
|
+
});
|
|
1500
1730
|
}
|
|
1501
1731
|
}
|
|
1502
1732
|
}
|
|
1503
1733
|
break;
|
|
1504
1734
|
}
|
|
1505
1735
|
case 'remove': {
|
|
1506
|
-
const items = step.
|
|
1736
|
+
const items = step.diff ?? [];
|
|
1507
1737
|
if (reverse) {
|
|
1508
1738
|
// 撤销 remove:按原 index 升序逐个插回(先小后大避免索引漂移)
|
|
1509
|
-
const sorted = [...items].sort((a, b) => a.index - b.index);
|
|
1510
|
-
for (const {
|
|
1739
|
+
const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
1740
|
+
for (const { oldSchema, parentId, index } of sorted) {
|
|
1741
|
+
if (!oldSchema || parentId === undefined) continue;
|
|
1511
1742
|
const parent = this.getNodeById(parentId, false) as MContainer | null;
|
|
1512
1743
|
if (parent?.items) {
|
|
1513
|
-
parent.items.splice(index, 0, cloneDeep(
|
|
1744
|
+
parent.items.splice(index ?? parent.items.length, 0, cloneDeep(oldSchema));
|
|
1514
1745
|
await stage?.add({
|
|
1515
|
-
config: cloneDeep(
|
|
1746
|
+
config: cloneDeep(oldSchema),
|
|
1516
1747
|
parent: cloneDeep(parent),
|
|
1517
1748
|
parentId,
|
|
1518
1749
|
root: cloneDeep(root),
|
|
@@ -1521,8 +1752,9 @@ class Editor extends BaseService {
|
|
|
1521
1752
|
}
|
|
1522
1753
|
} else {
|
|
1523
1754
|
// 重做 remove:再删一次
|
|
1524
|
-
for (const {
|
|
1525
|
-
|
|
1755
|
+
for (const { oldSchema } of items) {
|
|
1756
|
+
if (!oldSchema) continue;
|
|
1757
|
+
const existing = this.getNodeById(oldSchema.id, false);
|
|
1526
1758
|
if (existing) {
|
|
1527
1759
|
await this.remove(existing, commonOpts);
|
|
1528
1760
|
}
|
|
@@ -1531,27 +1763,31 @@ class Editor extends BaseService {
|
|
|
1531
1763
|
break;
|
|
1532
1764
|
}
|
|
1533
1765
|
case 'update': {
|
|
1534
|
-
const items = step.
|
|
1766
|
+
const items = step.diff ?? [];
|
|
1535
1767
|
// 优先按 changeRecords 局部 patch:仅触达 propPath 下的字段,避免整节点替换冲掉同节点上其它无关变更。
|
|
1536
1768
|
// 没有 changeRecords 的(如内部 sort/moveLayer/拖动等整节点快照场景)才退化为整节点替换。
|
|
1537
|
-
const configs = items
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1769
|
+
const configs = items
|
|
1770
|
+
.filter((item) => item.oldSchema && item.newSchema)
|
|
1771
|
+
.map(({ oldSchema, newSchema, changeRecords }) => {
|
|
1772
|
+
const oldNode = oldSchema!;
|
|
1773
|
+
const newNode = newSchema!;
|
|
1774
|
+
if (changeRecords?.length) {
|
|
1775
|
+
const sourceForValues = reverse ? oldNode : newNode;
|
|
1776
|
+
// 仅保留 id / type 作为最小骨架,再按 propPath 写入需要回滚/重做的字段;
|
|
1777
|
+
// 后续 update -> mergeWith 会与现有节点深合并,patch 中未涉及的字段不会被改动。
|
|
1778
|
+
const patch: MNode = { id: newNode.id, type: newNode.type };
|
|
1779
|
+
for (const record of changeRecords) {
|
|
1780
|
+
if (!record.propPath) {
|
|
1781
|
+
// 没有 propPath 视为整节点替换
|
|
1782
|
+
return cloneDeep(sourceForValues);
|
|
1783
|
+
}
|
|
1784
|
+
const value = cloneDeep(getValueByKeyPath(record.propPath, sourceForValues));
|
|
1785
|
+
setValueByKeyPath(record.propPath, value, patch);
|
|
1547
1786
|
}
|
|
1548
|
-
|
|
1549
|
-
setValueByKeyPath(record.propPath, value, patch);
|
|
1787
|
+
return patch;
|
|
1550
1788
|
}
|
|
1551
|
-
return
|
|
1552
|
-
}
|
|
1553
|
-
return cloneDeep(reverse ? oldNode : newNode);
|
|
1554
|
-
});
|
|
1789
|
+
return cloneDeep(reverse ? oldNode : newNode);
|
|
1790
|
+
});
|
|
1555
1791
|
if (configs.length) {
|
|
1556
1792
|
await this.update(configs, { doNotPushHistory: true });
|
|
1557
1793
|
}
|