@tmagic/editor 1.8.0-beta.2 → 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/Editor.vue_vue_type_script_setup_true_lang.js +9 -0
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +46 -28
- package/dist/es/editorProps.js +2 -0
- package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
- 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 +8 -4
- package/dist/es/index.js +3 -1
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +2 -5
- 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 +37 -14
- package/dist/es/layouts/history-list/BucketTab.js +5 -0
- package/dist/es/layouts/history-list/{CodeBlockTab.vue_vue_type_script_setup_true_lang.js → BucketTab.vue_vue_type_script_setup_true_lang.js} +30 -16
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +91 -60
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +82 -30
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +140 -66
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +15 -9
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +15 -6
- package/dist/es/layouts/history-list/composables.js +72 -2
- 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 +7 -2
- package/dist/es/services/keybinding.js +3 -3
- package/dist/es/style.css +60 -16
- package/dist/es/utils/content-menu.js +17 -9
- package/dist/style.css +60 -16
- package/dist/tmagic-editor.umd.cjs +795 -443
- package/package.json +7 -7
- package/src/Editor.vue +8 -0
- package/src/components/CompareForm.vue +50 -19
- package/src/editorProps.ts +7 -0
- package/src/fields/CodeLink.vue +2 -5
- 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 +4 -3
- package/src/index.ts +2 -0
- package/src/layouts/CodeEditor.vue +2 -5
- package/src/layouts/NavMenu.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +58 -29
- package/src/layouts/history-list/BucketTab.vue +80 -0
- package/src/layouts/history-list/GroupRow.vue +110 -58
- package/src/layouts/history-list/HistoryDiffDialog.vue +53 -33
- package/src/layouts/history-list/HistoryListPanel.vue +165 -52
- package/src/layouts/history-list/InitialRow.vue +17 -6
- package/src/layouts/history-list/PageTab.vue +25 -7
- package/src/layouts/history-list/composables.ts +92 -1
- 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 +10 -0
- package/src/services/keybinding.ts +3 -3
- package/src/theme/history-list-panel.scss +67 -14
- package/src/theme/props-panel.scss +3 -3
- package/src/type.ts +146 -0
- package/src/utils/content-menu.ts +18 -9
- package/types/index.d.ts +387 -156
- package/dist/es/layouts/history-list/CodeBlockTab.js +0 -5
- package/dist/es/layouts/history-list/DataSourceTab.js +0 -5
- package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +0 -62
- package/src/layouts/history-list/CodeBlockTab.vue +0 -61
- package/src/layouts/history-list/DataSourceTab.vue +0 -61
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,
|
|
@@ -254,6 +255,7 @@ class History extends BaseService {
|
|
|
254
255
|
public push(state: StepValue, pageId?: Id): StepValue | null {
|
|
255
256
|
const undoRedo = this.getUndoRedo(pageId);
|
|
256
257
|
if (!undoRedo) return null;
|
|
258
|
+
if (state.timestamp === undefined) state.timestamp = Date.now();
|
|
257
259
|
undoRedo.pushElement(state);
|
|
258
260
|
// 仅当推入的是当前活动页时才需要刷新 canUndo/canRedo —— 其它页栈对当前 UI 状态没影响。
|
|
259
261
|
if (pageId === undefined || `${pageId}` === `${this.state.pageId}`) {
|
|
@@ -279,6 +281,8 @@ class History extends BaseService {
|
|
|
279
281
|
changeRecords?: ChangeRecord[];
|
|
280
282
|
/** 可选的人类可读描述(如「修改按钮颜色」),仅用于历史面板展示。 */
|
|
281
283
|
historyDescription?: string;
|
|
284
|
+
/** 可选的操作途径(配置面板 / 菜单 / 接口等),仅用于历史面板展示与埋点。 */
|
|
285
|
+
source?: HistoryOpSource;
|
|
282
286
|
},
|
|
283
287
|
): CodeBlockStepValue | null {
|
|
284
288
|
if (!codeBlockId) return null;
|
|
@@ -289,6 +293,8 @@ class History extends BaseService {
|
|
|
289
293
|
newContent: payload.newContent ? cloneDeep(payload.newContent) : null,
|
|
290
294
|
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
|
291
295
|
historyDescription: payload.historyDescription,
|
|
296
|
+
source: payload.source,
|
|
297
|
+
timestamp: Date.now(),
|
|
292
298
|
};
|
|
293
299
|
|
|
294
300
|
this.getCodeBlockUndoRedo(codeBlockId).pushElement(step);
|
|
@@ -308,6 +314,8 @@ class History extends BaseService {
|
|
|
308
314
|
changeRecords?: ChangeRecord[];
|
|
309
315
|
/** 可选的人类可读描述,仅用于历史面板展示。 */
|
|
310
316
|
historyDescription?: string;
|
|
317
|
+
/** 可选的操作途径(配置面板 / 菜单 / 接口等),仅用于历史面板展示与埋点。 */
|
|
318
|
+
source?: HistoryOpSource;
|
|
311
319
|
},
|
|
312
320
|
): DataSourceStepValue | null {
|
|
313
321
|
if (!dataSourceId) return null;
|
|
@@ -318,6 +326,8 @@ class History extends BaseService {
|
|
|
318
326
|
newSchema: payload.newSchema ? cloneDeep(payload.newSchema) : null,
|
|
319
327
|
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
|
320
328
|
historyDescription: payload.historyDescription,
|
|
329
|
+
source: payload.source,
|
|
330
|
+
timestamp: Date.now(),
|
|
321
331
|
};
|
|
322
332
|
|
|
323
333
|
this.getDataSourceUndoRedo(dataSourceId).pushElement(step);
|
|
@@ -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;
|
|
@@ -125,19 +125,19 @@
|
|
|
125
125
|
&.is-merged {
|
|
126
126
|
margin: 4px 0;
|
|
127
127
|
padding: 4px 8px 6px;
|
|
128
|
-
background-color: rgba(
|
|
129
|
-
border: 1px solid rgba(
|
|
130
|
-
border-left: 3px solid #
|
|
128
|
+
background-color: rgba(47, 84, 235, 0.06);
|
|
129
|
+
border: 1px solid rgba(47, 84, 235, 0.18);
|
|
130
|
+
border-left: 3px solid #2f54eb;
|
|
131
131
|
border-radius: 4px;
|
|
132
132
|
|
|
133
133
|
// 卡片本体已经有背景色,hover 状态以更深的同色提示交互
|
|
134
134
|
&:hover {
|
|
135
|
-
background-color: rgba(
|
|
135
|
+
background-color: rgba(47, 84, 235, 0.1);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
.m-editor-history-list-group-head {
|
|
139
139
|
font-weight: 600;
|
|
140
|
-
color: #
|
|
140
|
+
color: #1d39c4;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// 已撤销态:整张卡片去色
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
margin: 6px 0 0 6px;
|
|
170
170
|
padding: 0;
|
|
171
171
|
list-style: none;
|
|
172
|
-
border-left: 1px dashed rgba(
|
|
172
|
+
border-left: 1px dashed rgba(47, 84, 235, 0.45);
|
|
173
173
|
|
|
174
174
|
li {
|
|
175
175
|
display: flex;
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
cursor: pointer;
|
|
186
186
|
|
|
187
187
|
&:hover {
|
|
188
|
-
background-color: rgba(
|
|
188
|
+
background-color: rgba(47, 84, 235, 0.1);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
@@ -222,6 +222,16 @@
|
|
|
222
222
|
white-space: nowrap;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// 操作时间:弱化展示,紧贴在描述之后、各操作按钮之前。
|
|
226
|
+
.m-editor-history-list-item-time {
|
|
227
|
+
flex: 0 0 auto;
|
|
228
|
+
color: #a8abb2;
|
|
229
|
+
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
font-weight: 400; // 防止被合并组头部的粗体继承
|
|
232
|
+
white-space: nowrap;
|
|
233
|
+
}
|
|
234
|
+
|
|
225
235
|
.m-editor-history-list-item-op {
|
|
226
236
|
flex: 0 0 auto;
|
|
227
237
|
padding: 0 6px;
|
|
@@ -240,7 +250,7 @@
|
|
|
240
250
|
}
|
|
241
251
|
|
|
242
252
|
&.op-update {
|
|
243
|
-
background-color: #
|
|
253
|
+
background-color: #e6a23c;
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
&.op-initial {
|
|
@@ -271,6 +281,20 @@
|
|
|
271
281
|
white-space: nowrap;
|
|
272
282
|
}
|
|
273
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
|
+
|
|
274
298
|
// 「合并 N 步」徽标:紫色实心胶囊,与合并组卡片色系一致,醒目区分单步条目。
|
|
275
299
|
.m-editor-history-list-item-merge {
|
|
276
300
|
flex: 0 0 auto;
|
|
@@ -279,7 +303,7 @@
|
|
|
279
303
|
font-size: 10px;
|
|
280
304
|
line-height: 16px;
|
|
281
305
|
color: #fff;
|
|
282
|
-
background-color: #
|
|
306
|
+
background-color: #2f54eb;
|
|
283
307
|
font-weight: 500;
|
|
284
308
|
letter-spacing: 0.2px;
|
|
285
309
|
}
|
|
@@ -300,21 +324,39 @@
|
|
|
300
324
|
}
|
|
301
325
|
}
|
|
302
326
|
|
|
327
|
+
// 「跳转」按钮:将历史游标移动到该 step,替代原先点击整行跳转的交互。
|
|
328
|
+
// 使用与组卡片一致的紫色色系,与「查看差异」「回滚」区分开。
|
|
329
|
+
.m-editor-history-list-item-goto {
|
|
330
|
+
flex: 0 0 auto;
|
|
331
|
+
padding: 0 6px;
|
|
332
|
+
border-radius: 2px;
|
|
333
|
+
font-size: 10px;
|
|
334
|
+
line-height: 16px;
|
|
335
|
+
color: #606266;
|
|
336
|
+
background-color: rgba(96, 98, 102, 0.1);
|
|
337
|
+
cursor: pointer;
|
|
338
|
+
user-select: none;
|
|
339
|
+
|
|
340
|
+
&:hover {
|
|
341
|
+
background-color: rgba(96, 98, 102, 0.18);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
303
345
|
// 「回滚」按钮:类 git revert,把目标 step 反向应用一次作为新提交。
|
|
304
|
-
//
|
|
346
|
+
// 使用红色色调,强调其为"破坏性/可逆操作",与「查看差异」「跳转」区分开。
|
|
305
347
|
.m-editor-history-list-item-revert {
|
|
306
348
|
flex: 0 0 auto;
|
|
307
349
|
padding: 0 6px;
|
|
308
350
|
border-radius: 2px;
|
|
309
351
|
font-size: 10px;
|
|
310
352
|
line-height: 16px;
|
|
311
|
-
color: #
|
|
312
|
-
background-color: rgba(
|
|
353
|
+
color: #f56c6c;
|
|
354
|
+
background-color: rgba(245, 108, 108, 0.12);
|
|
313
355
|
cursor: pointer;
|
|
314
356
|
user-select: none;
|
|
315
357
|
|
|
316
358
|
&:hover {
|
|
317
|
-
background-color: rgba(
|
|
359
|
+
background-color: rgba(245, 108, 108, 0.25);
|
|
318
360
|
}
|
|
319
361
|
}
|
|
320
362
|
|
|
@@ -369,6 +411,17 @@
|
|
|
369
411
|
flex-direction: column;
|
|
370
412
|
}
|
|
371
413
|
|
|
414
|
+
.m-editor-history-diff-dialog-notice {
|
|
415
|
+
margin-bottom: 8px;
|
|
416
|
+
padding: 8px 12px;
|
|
417
|
+
background-color: #fdf6ec;
|
|
418
|
+
border: 1px solid #faecd8;
|
|
419
|
+
border-radius: 4px;
|
|
420
|
+
color: #e6a23c;
|
|
421
|
+
font-size: 13px;
|
|
422
|
+
line-height: 1.5;
|
|
423
|
+
}
|
|
424
|
+
|
|
372
425
|
.m-editor-history-diff-dialog-header {
|
|
373
426
|
display: flex;
|
|
374
427
|
align-items: center;
|
|
@@ -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 {
|