@tmagic/editor 1.8.0-beta.3 → 1.8.0-beta.4
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 +15 -1
- package/dist/es/hooks/use-code-block-edit.js +6 -3
- package/dist/es/hooks/use-data-source-edit.js +5 -2
- package/dist/es/hooks/use-stage.js +6 -3
- package/dist/es/layouts/NavMenu.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 +6 -2
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +3 -0
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +42 -29
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/layouts/history-list/composables.js +53 -1
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
- package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/es/services/codeBlock.js +25 -10
- package/dist/es/services/dataSource.js +24 -10
- package/dist/es/services/editor.js +98 -46
- package/dist/es/services/history.js +2 -0
- package/dist/es/services/keybinding.js +3 -3
- package/dist/es/style.css +16 -4
- package/dist/es/utils/content-menu.js +17 -9
- package/dist/style.css +16 -4
- package/dist/tmagic-editor.umd.cjs +343 -144
- package/package.json +7 -7
- package/src/components/CompareForm.vue +19 -1
- package/src/hooks/use-code-block-edit.ts +4 -3
- package/src/hooks/use-data-source-edit.ts +2 -2
- package/src/hooks/use-stage.ts +3 -3
- package/src/layouts/NavMenu.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +6 -2
- package/src/layouts/history-list/BucketTab.vue +3 -0
- package/src/layouts/history-list/GroupRow.vue +20 -3
- package/src/layouts/history-list/HistoryDiffDialog.vue +2 -1
- package/src/layouts/history-list/HistoryListPanel.vue +4 -0
- package/src/layouts/history-list/PageTab.vue +5 -1
- package/src/layouts/history-list/composables.ts +60 -0
- package/src/layouts/props-panel/PropsPanel.vue +5 -1
- package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
- package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
- package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
- package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
- package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
- package/src/layouts/workspace/viewer/Stage.vue +1 -1
- package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
- package/src/services/codeBlock.ts +33 -9
- package/src/services/dataSource.ts +30 -8
- package/src/services/editor.ts +111 -34
- package/src/services/history.ts +7 -0
- package/src/services/keybinding.ts +3 -3
- package/src/theme/history-list-panel.scss +15 -1
- package/src/theme/props-panel.scss +3 -3
- package/src/type.ts +58 -2
- package/src/utils/content-menu.ts +18 -9
- package/types/index.d.ts +96 -25
|
@@ -120,9 +120,19 @@ class CodeBlock extends BaseService {
|
|
|
120
120
|
public async setCodeDslById(
|
|
121
121
|
id: Id,
|
|
122
122
|
codeConfig: Partial<CodeBlockContent>,
|
|
123
|
-
{
|
|
123
|
+
{
|
|
124
|
+
changeRecords,
|
|
125
|
+
doNotPushHistory = false,
|
|
126
|
+
historyDescription,
|
|
127
|
+
historySource,
|
|
128
|
+
}: HistoryOpOptionsWithChangeRecords = {},
|
|
124
129
|
): Promise<void> {
|
|
125
|
-
this.setCodeDslByIdSync(id, codeConfig, true, {
|
|
130
|
+
this.setCodeDslByIdSync(id, codeConfig, true, {
|
|
131
|
+
changeRecords,
|
|
132
|
+
doNotPushHistory,
|
|
133
|
+
historyDescription,
|
|
134
|
+
historySource,
|
|
135
|
+
});
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
/**
|
|
@@ -141,7 +151,12 @@ class CodeBlock extends BaseService {
|
|
|
141
151
|
id: Id,
|
|
142
152
|
codeConfig: Partial<CodeBlockContent>,
|
|
143
153
|
force = true,
|
|
144
|
-
{
|
|
154
|
+
{
|
|
155
|
+
changeRecords,
|
|
156
|
+
doNotPushHistory = false,
|
|
157
|
+
historyDescription,
|
|
158
|
+
historySource,
|
|
159
|
+
}: HistoryOpOptionsWithChangeRecords = {},
|
|
145
160
|
): void {
|
|
146
161
|
const codeDsl = this.getCodeDsl();
|
|
147
162
|
|
|
@@ -172,7 +187,13 @@ class CodeBlock extends BaseService {
|
|
|
172
187
|
const newContent = cloneDeep(codeDsl[id]);
|
|
173
188
|
|
|
174
189
|
if (!doNotPushHistory) {
|
|
175
|
-
historyService.pushCodeBlock(id, {
|
|
190
|
+
historyService.pushCodeBlock(id, {
|
|
191
|
+
oldContent,
|
|
192
|
+
newContent,
|
|
193
|
+
changeRecords,
|
|
194
|
+
historyDescription,
|
|
195
|
+
source: historySource,
|
|
196
|
+
});
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
this.emit('addOrUpdate', id, codeDsl[id]);
|
|
@@ -268,7 +289,7 @@ class CodeBlock extends BaseService {
|
|
|
268
289
|
*/
|
|
269
290
|
public async deleteCodeDslByIds(
|
|
270
291
|
codeIds: Id[],
|
|
271
|
-
{ doNotPushHistory = false, historyDescription }: HistoryOpOptions = {},
|
|
292
|
+
{ doNotPushHistory = false, historyDescription, historySource }: HistoryOpOptions = {},
|
|
272
293
|
): Promise<void> {
|
|
273
294
|
const currentDsl = await this.getCodeDsl();
|
|
274
295
|
|
|
@@ -281,7 +302,7 @@ class CodeBlock extends BaseService {
|
|
|
281
302
|
delete currentDsl[id];
|
|
282
303
|
|
|
283
304
|
if (oldContent && !doNotPushHistory) {
|
|
284
|
-
historyService.pushCodeBlock(id, { oldContent, newContent: null, historyDescription });
|
|
305
|
+
historyService.pushCodeBlock(id, { oldContent, newContent: null, historyDescription, source: historySource });
|
|
285
306
|
}
|
|
286
307
|
|
|
287
308
|
this.emit('remove', id);
|
|
@@ -373,6 +394,8 @@ class CodeBlock extends BaseService {
|
|
|
373
394
|
const list = historyService.getCodeBlockStepList(id);
|
|
374
395
|
const entry = list[index];
|
|
375
396
|
if (!entry?.applied) return null;
|
|
397
|
+
// 更新类步骤(前后 content 都存在)必须带 changeRecords 才支持回滚,否则只能整内容替换,会冲掉后续无关变更。
|
|
398
|
+
if (entry.step.oldContent && entry.step.newContent && !entry.step.changeRecords?.length) return null;
|
|
376
399
|
const description = `回滚 #${index + 1}: ${describeRevertCodeBlockStep(entry.step)}`;
|
|
377
400
|
return await this.applyRevertStep(entry.step, description);
|
|
378
401
|
}
|
|
@@ -471,13 +494,13 @@ class CodeBlock extends BaseService {
|
|
|
471
494
|
|
|
472
495
|
// 原本是新增 → revert 即删除
|
|
473
496
|
if (oldContent === null && newContent) {
|
|
474
|
-
await this.deleteCodeDslByIds([id], { historyDescription });
|
|
497
|
+
await this.deleteCodeDslByIds([id], { historyDescription, historySource: 'rollback' });
|
|
475
498
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
476
499
|
}
|
|
477
500
|
|
|
478
501
|
// 原本是删除 → revert 即写回
|
|
479
502
|
if (oldContent && newContent === null) {
|
|
480
|
-
this.setCodeDslByIdSync(id, cloneDeep(oldContent), true, { historyDescription });
|
|
503
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldContent), true, { historyDescription, historySource: 'rollback' });
|
|
481
504
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
482
505
|
}
|
|
483
506
|
|
|
@@ -500,11 +523,12 @@ class CodeBlock extends BaseService {
|
|
|
500
523
|
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep(oldContent) : patched, true, {
|
|
501
524
|
changeRecords,
|
|
502
525
|
historyDescription,
|
|
526
|
+
historySource: 'rollback',
|
|
503
527
|
});
|
|
504
528
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
505
529
|
}
|
|
506
530
|
|
|
507
|
-
this.setCodeDslByIdSync(id, cloneDeep(oldContent), true, { historyDescription });
|
|
531
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldContent), true, { historyDescription, historySource: 'rollback' });
|
|
508
532
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
509
533
|
}
|
|
510
534
|
|
|
@@ -129,7 +129,10 @@ class DataSource extends BaseService {
|
|
|
129
129
|
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
130
130
|
* @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
131
131
|
*/
|
|
132
|
-
public add(
|
|
132
|
+
public add(
|
|
133
|
+
config: DataSourceSchema,
|
|
134
|
+
{ doNotPushHistory = false, historyDescription, historySource }: HistoryOpOptions = {},
|
|
135
|
+
) {
|
|
133
136
|
const newConfig = {
|
|
134
137
|
...config,
|
|
135
138
|
id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId(),
|
|
@@ -138,7 +141,12 @@ class DataSource extends BaseService {
|
|
|
138
141
|
this.get('dataSources').push(newConfig);
|
|
139
142
|
|
|
140
143
|
if (!doNotPushHistory) {
|
|
141
|
-
historyService.pushDataSource(newConfig.id, {
|
|
144
|
+
historyService.pushDataSource(newConfig.id, {
|
|
145
|
+
oldSchema: null,
|
|
146
|
+
newSchema: newConfig,
|
|
147
|
+
historyDescription,
|
|
148
|
+
source: historySource,
|
|
149
|
+
});
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
this.emit('add', newConfig);
|
|
@@ -156,7 +164,12 @@ class DataSource extends BaseService {
|
|
|
156
164
|
*/
|
|
157
165
|
public update(
|
|
158
166
|
config: DataSourceSchema,
|
|
159
|
-
{
|
|
167
|
+
{
|
|
168
|
+
changeRecords = [],
|
|
169
|
+
doNotPushHistory = false,
|
|
170
|
+
historyDescription,
|
|
171
|
+
historySource,
|
|
172
|
+
}: HistoryOpOptionsWithChangeRecords = {},
|
|
160
173
|
) {
|
|
161
174
|
const dataSources = this.get('dataSources');
|
|
162
175
|
|
|
@@ -173,6 +186,7 @@ class DataSource extends BaseService {
|
|
|
173
186
|
newSchema: newConfig,
|
|
174
187
|
changeRecords,
|
|
175
188
|
historyDescription,
|
|
189
|
+
source: historySource,
|
|
176
190
|
});
|
|
177
191
|
}
|
|
178
192
|
|
|
@@ -191,14 +205,19 @@ class DataSource extends BaseService {
|
|
|
191
205
|
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
192
206
|
* @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
193
207
|
*/
|
|
194
|
-
public remove(id: string, { doNotPushHistory = false, historyDescription }: HistoryOpOptions = {}) {
|
|
208
|
+
public remove(id: string, { doNotPushHistory = false, historyDescription, historySource }: HistoryOpOptions = {}) {
|
|
195
209
|
const dataSources = this.get('dataSources');
|
|
196
210
|
const index = dataSources.findIndex((ds) => ds.id === id);
|
|
197
211
|
const oldConfig = index !== -1 ? dataSources[index] : null;
|
|
198
212
|
dataSources.splice(index, 1);
|
|
199
213
|
|
|
200
214
|
if (oldConfig && !doNotPushHistory) {
|
|
201
|
-
historyService.pushDataSource(id, {
|
|
215
|
+
historyService.pushDataSource(id, {
|
|
216
|
+
oldSchema: cloneDeep(oldConfig),
|
|
217
|
+
newSchema: null,
|
|
218
|
+
historyDescription,
|
|
219
|
+
source: historySource,
|
|
220
|
+
});
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
this.emit('remove', id);
|
|
@@ -278,6 +297,8 @@ class DataSource extends BaseService {
|
|
|
278
297
|
const list = historyService.getDataSourceStepList(id);
|
|
279
298
|
const entry = list[index];
|
|
280
299
|
if (!entry?.applied) return null;
|
|
300
|
+
// 更新类步骤(前后 schema 都存在)必须带 changeRecords 才支持回滚,否则只能整 schema 替换,会冲掉后续无关变更。
|
|
301
|
+
if (entry.step.oldSchema && entry.step.newSchema && !entry.step.changeRecords?.length) return null;
|
|
281
302
|
const description = `回滚 #${index + 1}: ${describeRevertDataSourceStep(entry.step)}`;
|
|
282
303
|
return this.applyRevertStep(entry.step, description);
|
|
283
304
|
}
|
|
@@ -366,13 +387,13 @@ class DataSource extends BaseService {
|
|
|
366
387
|
|
|
367
388
|
// 原本是新增 → revert 即删除
|
|
368
389
|
if (oldSchema === null && newSchema) {
|
|
369
|
-
this.remove(`${id}`, { historyDescription });
|
|
390
|
+
this.remove(`${id}`, { historyDescription, historySource: 'rollback' });
|
|
370
391
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
371
392
|
}
|
|
372
393
|
|
|
373
394
|
// 原本是删除 → revert 即重新加回
|
|
374
395
|
if (oldSchema && newSchema === null) {
|
|
375
|
-
this.add(cloneDeep(oldSchema), { historyDescription });
|
|
396
|
+
this.add(cloneDeep(oldSchema), { historyDescription, historySource: 'rollback' });
|
|
376
397
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
377
398
|
}
|
|
378
399
|
|
|
@@ -395,11 +416,12 @@ class DataSource extends BaseService {
|
|
|
395
416
|
this.update(fallbackToFullReplace ? cloneDeep(oldSchema) : patched, {
|
|
396
417
|
changeRecords,
|
|
397
418
|
historyDescription,
|
|
419
|
+
historySource: 'rollback',
|
|
398
420
|
});
|
|
399
421
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
400
422
|
}
|
|
401
423
|
|
|
402
|
-
this.update(cloneDeep(oldSchema), { historyDescription });
|
|
424
|
+
this.update(cloneDeep(oldSchema), { historyDescription, historySource: 'rollback' });
|
|
403
425
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
404
426
|
}
|
|
405
427
|
|
package/src/services/editor.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
DslOpOptions,
|
|
37
37
|
EditorEvents,
|
|
38
38
|
EditorNodeInfo,
|
|
39
|
+
HistoryOpSource,
|
|
39
40
|
HistoryOpType,
|
|
40
41
|
PastePosition,
|
|
41
42
|
StepValue,
|
|
@@ -406,7 +407,13 @@ class Editor extends BaseService {
|
|
|
406
407
|
public async add(
|
|
407
408
|
addNode: AddMNode | MNode[],
|
|
408
409
|
parent?: MContainer | null,
|
|
409
|
-
{
|
|
410
|
+
{
|
|
411
|
+
doNotSelect = false,
|
|
412
|
+
doNotSwitchPage = false,
|
|
413
|
+
doNotPushHistory = false,
|
|
414
|
+
historyDescription,
|
|
415
|
+
historySource,
|
|
416
|
+
}: DslOpOptions = {},
|
|
410
417
|
): Promise<MNode | MNode[]> {
|
|
411
418
|
this.captureSelectionBeforeOp();
|
|
412
419
|
|
|
@@ -466,9 +473,8 @@ class Editor extends BaseService {
|
|
|
466
473
|
if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
|
|
467
474
|
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
|
468
475
|
if (!doNotPushHistory) {
|
|
469
|
-
this.pushOpHistory(
|
|
470
|
-
|
|
471
|
-
{
|
|
476
|
+
this.pushOpHistory('add', {
|
|
477
|
+
extra: {
|
|
472
478
|
nodes: newNodes.map((n) => cloneDeep(toRaw(n))),
|
|
473
479
|
parentId: (this.getParentById(newNodes[0].id, false) ?? this.get('root'))!.id,
|
|
474
480
|
indexMap: Object.fromEntries(
|
|
@@ -478,9 +484,10 @@ class Editor extends BaseService {
|
|
|
478
484
|
}),
|
|
479
485
|
),
|
|
480
486
|
},
|
|
481
|
-
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
487
|
+
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
482
488
|
historyDescription,
|
|
483
|
-
|
|
489
|
+
source: historySource,
|
|
490
|
+
});
|
|
484
491
|
} else {
|
|
485
492
|
this.selectionBeforeOp = null;
|
|
486
493
|
}
|
|
@@ -577,7 +584,13 @@ class Editor extends BaseService {
|
|
|
577
584
|
*/
|
|
578
585
|
public async remove(
|
|
579
586
|
nodeOrNodeList: MNode | MNode[],
|
|
580
|
-
{
|
|
587
|
+
{
|
|
588
|
+
doNotSelect = false,
|
|
589
|
+
doNotSwitchPage = false,
|
|
590
|
+
doNotPushHistory = false,
|
|
591
|
+
historyDescription,
|
|
592
|
+
historySource,
|
|
593
|
+
}: DslOpOptions = {},
|
|
581
594
|
): Promise<void> {
|
|
582
595
|
this.captureSelectionBeforeOp();
|
|
583
596
|
|
|
@@ -606,7 +619,12 @@ class Editor extends BaseService {
|
|
|
606
619
|
|
|
607
620
|
if (removedItems.length > 0 && pageForOp) {
|
|
608
621
|
if (!doNotPushHistory) {
|
|
609
|
-
this.pushOpHistory('remove', {
|
|
622
|
+
this.pushOpHistory('remove', {
|
|
623
|
+
extra: { removedItems },
|
|
624
|
+
pageData: pageForOp,
|
|
625
|
+
historyDescription,
|
|
626
|
+
source: historySource,
|
|
627
|
+
});
|
|
610
628
|
} else {
|
|
611
629
|
this.selectionBeforeOp = null;
|
|
612
630
|
}
|
|
@@ -704,11 +722,12 @@ class Editor extends BaseService {
|
|
|
704
722
|
changeRecordList?: ChangeRecord[][];
|
|
705
723
|
doNotPushHistory?: boolean;
|
|
706
724
|
historyDescription?: string;
|
|
725
|
+
historySource?: HistoryOpSource;
|
|
707
726
|
} = {},
|
|
708
727
|
): Promise<MNode | MNode[]> {
|
|
709
728
|
this.captureSelectionBeforeOp();
|
|
710
729
|
|
|
711
|
-
const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription } = data;
|
|
730
|
+
const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription, historySource } = data;
|
|
712
731
|
|
|
713
732
|
const nodes = Array.isArray(config) ? config : [config];
|
|
714
733
|
|
|
@@ -726,9 +745,8 @@ class Editor extends BaseService {
|
|
|
726
745
|
if (curNodes.length) {
|
|
727
746
|
if (!doNotPushHistory) {
|
|
728
747
|
const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
|
|
729
|
-
this.pushOpHistory(
|
|
730
|
-
|
|
731
|
-
{
|
|
748
|
+
this.pushOpHistory('update', {
|
|
749
|
+
extra: {
|
|
732
750
|
updatedItems: updateData.map((d) => ({
|
|
733
751
|
oldNode: cloneDeep(d.oldNode),
|
|
734
752
|
newNode: cloneDeep(toRaw(d.newNode)),
|
|
@@ -737,9 +755,10 @@ class Editor extends BaseService {
|
|
|
737
755
|
changeRecords: d.changeRecords?.length ? cloneDeep(d.changeRecords) : undefined,
|
|
738
756
|
})),
|
|
739
757
|
},
|
|
740
|
-
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
758
|
+
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
741
759
|
historyDescription,
|
|
742
|
-
|
|
760
|
+
source: historySource,
|
|
761
|
+
});
|
|
743
762
|
} else {
|
|
744
763
|
this.selectionBeforeOp = null;
|
|
745
764
|
}
|
|
@@ -763,7 +782,7 @@ class Editor extends BaseService {
|
|
|
763
782
|
public async sort(
|
|
764
783
|
id1: Id,
|
|
765
784
|
id2: Id,
|
|
766
|
-
{ doNotSelect = false, doNotPushHistory = false }: DslOpOptions = {},
|
|
785
|
+
{ doNotSelect = false, doNotPushHistory = false, historySource }: DslOpOptions = {},
|
|
767
786
|
): Promise<void> {
|
|
768
787
|
this.captureSelectionBeforeOp();
|
|
769
788
|
|
|
@@ -783,7 +802,7 @@ class Editor extends BaseService {
|
|
|
783
802
|
|
|
784
803
|
parent.items.splice(index2, 0, ...parent.items.splice(index1, 1));
|
|
785
804
|
|
|
786
|
-
await this.update(parent, { doNotPushHistory });
|
|
805
|
+
await this.update(parent, { doNotPushHistory, historySource });
|
|
787
806
|
if (!doNotSelect) {
|
|
788
807
|
await this.select(node);
|
|
789
808
|
}
|
|
@@ -836,7 +855,13 @@ class Editor extends BaseService {
|
|
|
836
855
|
public async paste(
|
|
837
856
|
position: PastePosition = {},
|
|
838
857
|
collectorOptions?: TargetOptions,
|
|
839
|
-
{
|
|
858
|
+
{
|
|
859
|
+
doNotSelect = false,
|
|
860
|
+
doNotSwitchPage = false,
|
|
861
|
+
doNotPushHistory = false,
|
|
862
|
+
historyDescription,
|
|
863
|
+
historySource,
|
|
864
|
+
}: DslOpOptions = {},
|
|
840
865
|
): Promise<MNode | MNode[] | void> {
|
|
841
866
|
const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
|
|
842
867
|
if (!Array.isArray(config)) return;
|
|
@@ -857,7 +882,13 @@ class Editor extends BaseService {
|
|
|
857
882
|
propsService.replaceRelateId(config, pasteConfigs, collectorOptions);
|
|
858
883
|
}
|
|
859
884
|
|
|
860
|
-
return this.add(pasteConfigs, parent, {
|
|
885
|
+
return this.add(pasteConfigs, parent, {
|
|
886
|
+
doNotSelect,
|
|
887
|
+
doNotSwitchPage,
|
|
888
|
+
doNotPushHistory,
|
|
889
|
+
historyDescription,
|
|
890
|
+
historySource,
|
|
891
|
+
});
|
|
861
892
|
}
|
|
862
893
|
|
|
863
894
|
public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> {
|
|
@@ -893,14 +924,14 @@ class Editor extends BaseService {
|
|
|
893
924
|
*/
|
|
894
925
|
public async alignCenter(
|
|
895
926
|
config: MNode | MNode[],
|
|
896
|
-
{ doNotSelect = false, doNotPushHistory = false }: DslOpOptions = {},
|
|
927
|
+
{ doNotSelect = false, doNotPushHistory = false, historyDescription, historySource }: DslOpOptions = {},
|
|
897
928
|
): Promise<MNode | MNode[]> {
|
|
898
929
|
const nodes = Array.isArray(config) ? config : [config];
|
|
899
930
|
const stage = this.get('stage');
|
|
900
931
|
|
|
901
932
|
const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
|
|
902
933
|
|
|
903
|
-
const newNode = await this.update(newNodes, { doNotPushHistory });
|
|
934
|
+
const newNode = await this.update(newNodes, { doNotPushHistory, historyDescription, historySource });
|
|
904
935
|
|
|
905
936
|
if (!doNotSelect) {
|
|
906
937
|
if (newNodes.length > 1) {
|
|
@@ -919,7 +950,10 @@ class Editor extends BaseService {
|
|
|
919
950
|
* @param options 可选配置
|
|
920
951
|
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
921
952
|
*/
|
|
922
|
-
public async moveLayer(
|
|
953
|
+
public async moveLayer(
|
|
954
|
+
offset: number | LayerOffset,
|
|
955
|
+
{ doNotPushHistory = false, historyDescription, historySource }: DslOpOptions = {},
|
|
956
|
+
): Promise<void> {
|
|
923
957
|
this.captureSelectionBeforeOp();
|
|
924
958
|
|
|
925
959
|
const root = this.get('root');
|
|
@@ -960,10 +994,15 @@ class Editor extends BaseService {
|
|
|
960
994
|
const pageForOp = this.getNodeInfo(node.id, false).page;
|
|
961
995
|
this.pushOpHistory(
|
|
962
996
|
'update',
|
|
997
|
+
|
|
963
998
|
{
|
|
964
|
-
|
|
999
|
+
extra: {
|
|
1000
|
+
updatedItems: [{ oldNode: oldParent, newNode: cloneDeep(toRaw(parent)) }],
|
|
1001
|
+
},
|
|
1002
|
+
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
1003
|
+
historyDescription,
|
|
1004
|
+
source: historySource,
|
|
965
1005
|
},
|
|
966
|
-
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
967
1006
|
);
|
|
968
1007
|
} else {
|
|
969
1008
|
this.selectionBeforeOp = null;
|
|
@@ -989,7 +1028,13 @@ class Editor extends BaseService {
|
|
|
989
1028
|
public async moveToContainer(
|
|
990
1029
|
config: MNode | MNode[],
|
|
991
1030
|
targetId: Id,
|
|
992
|
-
{
|
|
1031
|
+
{
|
|
1032
|
+
doNotSelect = false,
|
|
1033
|
+
doNotSwitchPage = false,
|
|
1034
|
+
doNotPushHistory = false,
|
|
1035
|
+
historyDescription,
|
|
1036
|
+
historySource,
|
|
1037
|
+
}: DslOpOptions = {},
|
|
993
1038
|
): Promise<MNode | MNode[]> {
|
|
994
1039
|
const isBatch = Array.isArray(config);
|
|
995
1040
|
const configs = (isBatch ? config : [config]).filter((item) => !(isPage(item) || isPageFragment(item)));
|
|
@@ -1052,7 +1097,12 @@ class Editor extends BaseService {
|
|
|
1052
1097
|
newNode: cloneDeep(toRaw(this.getNodeById(id, false))) as MNode,
|
|
1053
1098
|
}));
|
|
1054
1099
|
const historyPage = moves[0].pageForOp ?? { name: '', id: target.id };
|
|
1055
|
-
this.pushOpHistory('update', {
|
|
1100
|
+
this.pushOpHistory('update', {
|
|
1101
|
+
extra: { updatedItems },
|
|
1102
|
+
pageData: historyPage,
|
|
1103
|
+
historyDescription,
|
|
1104
|
+
source: historySource,
|
|
1105
|
+
});
|
|
1056
1106
|
} else {
|
|
1057
1107
|
this.selectionBeforeOp = null;
|
|
1058
1108
|
}
|
|
@@ -1064,7 +1114,7 @@ class Editor extends BaseService {
|
|
|
1064
1114
|
config: MNode | MNode[],
|
|
1065
1115
|
targetParent: MContainer,
|
|
1066
1116
|
targetIndex: number,
|
|
1067
|
-
{ doNotPushHistory = false }: DslOpOptions = {},
|
|
1117
|
+
{ doNotPushHistory = false, historyDescription, historySource }: DslOpOptions = {},
|
|
1068
1118
|
) {
|
|
1069
1119
|
this.captureSelectionBeforeOp();
|
|
1070
1120
|
|
|
@@ -1127,7 +1177,12 @@ class Editor extends BaseService {
|
|
|
1127
1177
|
}
|
|
1128
1178
|
if (!doNotPushHistory) {
|
|
1129
1179
|
const pageForOp = this.getNodeInfo(configs[0].id, false).page;
|
|
1130
|
-
this.pushOpHistory('update', {
|
|
1180
|
+
this.pushOpHistory('update', {
|
|
1181
|
+
extra: { updatedItems },
|
|
1182
|
+
pageData: { name: pageForOp?.name || '', id: pageForOp!.id },
|
|
1183
|
+
historyDescription,
|
|
1184
|
+
source: historySource,
|
|
1185
|
+
});
|
|
1131
1186
|
} else {
|
|
1132
1187
|
this.selectionBeforeOp = null;
|
|
1133
1188
|
}
|
|
@@ -1184,6 +1239,12 @@ class Editor extends BaseService {
|
|
|
1184
1239
|
const root = this.get('root');
|
|
1185
1240
|
if (!root) return null;
|
|
1186
1241
|
|
|
1242
|
+
// 更新类步骤必须带 changeRecords 才支持回滚:缺失时只能整节点替换,会冲掉后续无关变更,故不支持。
|
|
1243
|
+
if (step.opType === 'update') {
|
|
1244
|
+
const items = step.updatedItems ?? [];
|
|
1245
|
+
if (!items.length || !items.every((item) => item.changeRecords?.length)) return null;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1187
1248
|
// 反向应用产生的新 step 由内部 pushOpHistory 触发 history `change` 事件,监听一次以拿到引用。
|
|
1188
1249
|
let revertedStep: StepValue | null = null;
|
|
1189
1250
|
const captureRevert = (s: StepValue) => {
|
|
@@ -1193,7 +1254,7 @@ class Editor extends BaseService {
|
|
|
1193
1254
|
|
|
1194
1255
|
const historyDescription = `回滚 #${index + 1}: ${describeStepForRevert(step)}`;
|
|
1195
1256
|
// revert 走 public add/remove/update,让操作以一条普通新 step 入栈;不要切换选区与页面,避免打断用户。
|
|
1196
|
-
const opts = { doNotSelect: true, doNotSwitchPage: true, historyDescription } as const;
|
|
1257
|
+
const opts = { doNotSelect: true, doNotSwitchPage: true, historyDescription, historySource: 'rollback' } as const;
|
|
1197
1258
|
|
|
1198
1259
|
try {
|
|
1199
1260
|
switch (step.opType) {
|
|
@@ -1241,7 +1302,7 @@ class Editor extends BaseService {
|
|
|
1241
1302
|
return cloneDeep(oldNode);
|
|
1242
1303
|
});
|
|
1243
1304
|
if (configs.length) {
|
|
1244
|
-
await this.update(configs, { historyDescription });
|
|
1305
|
+
await this.update(configs, { historyDescription, historySource: 'rollback' });
|
|
1245
1306
|
}
|
|
1246
1307
|
break;
|
|
1247
1308
|
}
|
|
@@ -1285,14 +1346,21 @@ class Editor extends BaseService {
|
|
|
1285
1346
|
return cursor;
|
|
1286
1347
|
}
|
|
1287
1348
|
|
|
1288
|
-
public async move(
|
|
1349
|
+
public async move(
|
|
1350
|
+
left: number,
|
|
1351
|
+
top: number,
|
|
1352
|
+
{ doNotPushHistory = false, historyDescription, historySource }: DslOpOptions = {},
|
|
1353
|
+
) {
|
|
1289
1354
|
const node = toRaw(this.get('node'));
|
|
1290
1355
|
if (!node || isPage(node)) return;
|
|
1291
1356
|
|
|
1292
1357
|
const newStyle = calcMoveStyle(node.style || {}, left, top);
|
|
1293
1358
|
if (!newStyle) return;
|
|
1294
1359
|
|
|
1295
|
-
await this.update(
|
|
1360
|
+
await this.update(
|
|
1361
|
+
{ id: node.id, type: node.type, style: newStyle },
|
|
1362
|
+
{ doNotPushHistory, historyDescription, historySource },
|
|
1363
|
+
);
|
|
1296
1364
|
}
|
|
1297
1365
|
|
|
1298
1366
|
public resetState() {
|
|
@@ -1350,9 +1418,17 @@ class Editor extends BaseService {
|
|
|
1350
1418
|
|
|
1351
1419
|
private pushOpHistory(
|
|
1352
1420
|
opType: HistoryOpType,
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1421
|
+
{
|
|
1422
|
+
extra,
|
|
1423
|
+
pageData,
|
|
1424
|
+
historyDescription,
|
|
1425
|
+
source,
|
|
1426
|
+
}: {
|
|
1427
|
+
extra: Partial<StepValue>;
|
|
1428
|
+
pageData: { name: string; id: Id };
|
|
1429
|
+
historyDescription?: string;
|
|
1430
|
+
source?: HistoryOpSource;
|
|
1431
|
+
},
|
|
1356
1432
|
) {
|
|
1357
1433
|
const step: StepValue = {
|
|
1358
1434
|
data: pageData,
|
|
@@ -1363,6 +1439,7 @@ class Editor extends BaseService {
|
|
|
1363
1439
|
...extra,
|
|
1364
1440
|
};
|
|
1365
1441
|
if (historyDescription) step.historyDescription = historyDescription;
|
|
1442
|
+
if (source) step.source = source;
|
|
1366
1443
|
// 显式按 step.data.id 入栈:跨页操作(如 moveToContainer 从源页搬到目标页)
|
|
1367
1444
|
// 必须落到正确的页面栈,否则会把记录错误地推到当前活动页 / 操作发起页。
|
|
1368
1445
|
historyService.push(step, pageData.id);
|
package/src/services/history.ts
CHANGED
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
CodeBlockStepValue,
|
|
28
28
|
DataSourceHistoryGroup,
|
|
29
29
|
DataSourceStepValue,
|
|
30
|
+
HistoryOpSource,
|
|
30
31
|
HistoryState,
|
|
31
32
|
PageHistoryGroup,
|
|
32
33
|
PageHistoryStepEntry,
|
|
@@ -280,6 +281,8 @@ class History extends BaseService {
|
|
|
280
281
|
changeRecords?: ChangeRecord[];
|
|
281
282
|
/** 可选的人类可读描述(如「修改按钮颜色」),仅用于历史面板展示。 */
|
|
282
283
|
historyDescription?: string;
|
|
284
|
+
/** 可选的操作途径(配置面板 / 菜单 / 接口等),仅用于历史面板展示与埋点。 */
|
|
285
|
+
source?: HistoryOpSource;
|
|
283
286
|
},
|
|
284
287
|
): CodeBlockStepValue | null {
|
|
285
288
|
if (!codeBlockId) return null;
|
|
@@ -290,6 +293,7 @@ class History extends BaseService {
|
|
|
290
293
|
newContent: payload.newContent ? cloneDeep(payload.newContent) : null,
|
|
291
294
|
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
|
292
295
|
historyDescription: payload.historyDescription,
|
|
296
|
+
source: payload.source,
|
|
293
297
|
timestamp: Date.now(),
|
|
294
298
|
};
|
|
295
299
|
|
|
@@ -310,6 +314,8 @@ class History extends BaseService {
|
|
|
310
314
|
changeRecords?: ChangeRecord[];
|
|
311
315
|
/** 可选的人类可读描述,仅用于历史面板展示。 */
|
|
312
316
|
historyDescription?: string;
|
|
317
|
+
/** 可选的操作途径(配置面板 / 菜单 / 接口等),仅用于历史面板展示与埋点。 */
|
|
318
|
+
source?: HistoryOpSource;
|
|
313
319
|
},
|
|
314
320
|
): DataSourceStepValue | null {
|
|
315
321
|
if (!dataSourceId) return null;
|
|
@@ -320,6 +326,7 @@ class History extends BaseService {
|
|
|
320
326
|
newSchema: payload.newSchema ? cloneDeep(payload.newSchema) : null,
|
|
321
327
|
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
|
322
328
|
historyDescription: payload.historyDescription,
|
|
329
|
+
source: payload.source,
|
|
323
330
|
timestamp: Date.now(),
|
|
324
331
|
};
|
|
325
332
|
|
|
@@ -20,7 +20,7 @@ class Keybinding extends BaseService {
|
|
|
20
20
|
const nodes = editorService.get('nodes');
|
|
21
21
|
|
|
22
22
|
if (!nodes || isPage(nodes[0]) || isPageFragment(nodes[0])) return;
|
|
23
|
-
editorService.remove(nodes);
|
|
23
|
+
editorService.remove(nodes, { historySource: 'shortcut' });
|
|
24
24
|
},
|
|
25
25
|
[KeyBindingCommand.COPY_NODE]: () => {
|
|
26
26
|
const nodes = editorService.get('nodes');
|
|
@@ -31,11 +31,11 @@ class Keybinding extends BaseService {
|
|
|
31
31
|
|
|
32
32
|
if (!nodes || isPage(nodes[0]) || isPageFragment(nodes[0])) return;
|
|
33
33
|
editorService.copy(nodes);
|
|
34
|
-
editorService.remove(nodes);
|
|
34
|
+
editorService.remove(nodes, { historySource: 'shortcut' });
|
|
35
35
|
},
|
|
36
36
|
[KeyBindingCommand.PASTE_NODE]: () => {
|
|
37
37
|
const nodes = editorService.get('nodes');
|
|
38
|
-
nodes && editorService.paste({ offsetX: 10, offsetY: 10 });
|
|
38
|
+
nodes && editorService.paste({ offsetX: 10, offsetY: 10 }, undefined, { historySource: 'shortcut' });
|
|
39
39
|
},
|
|
40
40
|
[KeyBindingCommand.UNDO]: () => {
|
|
41
41
|
editorService.undo();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
position: absolute;
|
|
14
14
|
top: 4px;
|
|
15
15
|
right: 4px;
|
|
16
|
-
z-index:
|
|
16
|
+
z-index: 2;
|
|
17
17
|
display: flex;
|
|
18
18
|
align-items: center;
|
|
19
19
|
height: 40px;
|
|
@@ -281,6 +281,20 @@
|
|
|
281
281
|
white-space: nowrap;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// 「操作途径」徽标:浅灰描边胶囊,弱化展示来源(画布 / 图层 / 配置面板…),不抢占描述焦点。
|
|
285
|
+
.m-editor-history-list-item-source {
|
|
286
|
+
flex: 0 0 auto;
|
|
287
|
+
padding: 0 6px;
|
|
288
|
+
border: 1px solid #dcdfe6;
|
|
289
|
+
border-radius: 8px;
|
|
290
|
+
font-size: 10px;
|
|
291
|
+
line-height: 14px;
|
|
292
|
+
color: #909399;
|
|
293
|
+
background-color: #f4f4f5;
|
|
294
|
+
white-space: nowrap;
|
|
295
|
+
font-weight: 400; // 防止被合并组头部的粗体继承
|
|
296
|
+
}
|
|
297
|
+
|
|
284
298
|
// 「合并 N 步」徽标:紫色实心胶囊,与合并组卡片色系一致,醒目区分单步条目。
|
|
285
299
|
.m-editor-history-list-item-merge {
|
|
286
300
|
flex: 0 0 auto;
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
position: absolute;
|
|
59
59
|
right: 15px;
|
|
60
60
|
bottom: 15px;
|
|
61
|
-
z-index:
|
|
61
|
+
z-index: 32;
|
|
62
62
|
opacity: 0.5;
|
|
63
63
|
|
|
64
64
|
&:hover {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
position: absolute;
|
|
71
71
|
right: 15px;
|
|
72
72
|
bottom: 60px;
|
|
73
|
-
z-index:
|
|
73
|
+
z-index: 31;
|
|
74
74
|
opacity: 0.5;
|
|
75
75
|
|
|
76
76
|
&:hover {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
position: absolute;
|
|
83
83
|
left: 0;
|
|
84
84
|
top: 0;
|
|
85
|
-
z-index:
|
|
85
|
+
z-index: 31;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.m-editor-resizer {
|