@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
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.4",
|
|
3
3
|
"name": "@tmagic/editor",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"moveable": "^0.53.0",
|
|
59
59
|
"serialize-javascript": "^7.0.0",
|
|
60
60
|
"sortablejs": "^1.15.6",
|
|
61
|
-
"@tmagic/design": "1.8.0-beta.
|
|
62
|
-
"@tmagic/form": "1.8.0-beta.
|
|
63
|
-
"@tmagic/stage": "1.8.0-beta.
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/
|
|
61
|
+
"@tmagic/design": "1.8.0-beta.4",
|
|
62
|
+
"@tmagic/form": "1.8.0-beta.4",
|
|
63
|
+
"@tmagic/stage": "1.8.0-beta.4",
|
|
64
|
+
"@tmagic/table": "1.8.0-beta.4",
|
|
65
|
+
"@tmagic/utils": "1.8.0-beta.4"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/events": "^3.0.3",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"type-fest": "^5.2.0",
|
|
77
77
|
"typescript": "^6.0.3",
|
|
78
78
|
"vue": "^3.5.34",
|
|
79
|
-
"@tmagic/core": "1.8.0-beta.
|
|
79
|
+
"@tmagic/core": "1.8.0-beta.4"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"typescript": {
|
|
@@ -152,6 +152,24 @@ const showDiff = ({ curValue, lastValue, config }: { curValue: any; lastValue: a
|
|
|
152
152
|
return !isEqual(curValue, lastValue);
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
+
const removeStyleDisplayConfig = (formConfig: FormConfig): FormConfig =>
|
|
156
|
+
formConfig.map((item) => {
|
|
157
|
+
if (!('type' in item)) return item;
|
|
158
|
+
if (item?.type !== 'tab' || !Array.isArray(item.items)) return item;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
...item,
|
|
162
|
+
items: item.items.map((tabPane) => {
|
|
163
|
+
if (tabPane?.title !== '样式' || !Array.isArray(tabPane.items)) return tabPane;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...tabPane,
|
|
167
|
+
display: true,
|
|
168
|
+
};
|
|
169
|
+
}),
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
155
173
|
/**
|
|
156
174
|
* 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
|
|
157
175
|
* 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
|
|
@@ -162,7 +180,7 @@ const defaultLoadConfig = async (): Promise<FormConfig> => {
|
|
|
162
180
|
if (!props.type) {
|
|
163
181
|
return [];
|
|
164
182
|
}
|
|
165
|
-
return await propsService.getPropsConfig(props.type);
|
|
183
|
+
return removeStyleDisplayConfig(await propsService.getPropsConfig(props.type));
|
|
166
184
|
}
|
|
167
185
|
case 'data-source': {
|
|
168
186
|
return dataSourceService.getFormConfig(props.type || 'base');
|
|
@@ -6,7 +6,7 @@ import { tMagicMessage } from '@tmagic/design';
|
|
|
6
6
|
import type { ContainerChangeEventData } from '@tmagic/form';
|
|
7
7
|
|
|
8
8
|
import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
|
|
9
|
-
import type { Services } from '@editor/type';
|
|
9
|
+
import type { HistoryOpSource, Services } from '@editor/type';
|
|
10
10
|
|
|
11
11
|
export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService']) => {
|
|
12
12
|
const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
|
|
@@ -58,8 +58,8 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// 删除代码块
|
|
61
|
-
const deleteCode = async (key: string) => {
|
|
62
|
-
codeBlockService.deleteCodeDslByIds([key]);
|
|
61
|
+
const deleteCode = async (key: string, { historySource }: { historySource?: HistoryOpSource } = {}) => {
|
|
62
|
+
codeBlockService.deleteCodeDslByIds([key], { historySource });
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const submitCodeBlockHandler = async (values: CodeBlockContent, eventData?: ContainerChangeEventData) => {
|
|
@@ -67,6 +67,7 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
|
|
|
67
67
|
|
|
68
68
|
await codeBlockService.setCodeDslById(codeId.value, values, {
|
|
69
69
|
changeRecords: eventData?.changeRecords,
|
|
70
|
+
historySource: 'props',
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
codeBlockEditorRef.value?.hide();
|
|
@@ -27,9 +27,9 @@ export const useDataSourceEdit = (dataSourceService: Services['dataSourceService
|
|
|
27
27
|
|
|
28
28
|
const submitDataSourceHandler = (value: DataSourceSchema, eventData: ContainerChangeEventData) => {
|
|
29
29
|
if (value.id) {
|
|
30
|
-
dataSourceService.update(value, { changeRecords: eventData.changeRecords });
|
|
30
|
+
dataSourceService.update(value, { changeRecords: eventData.changeRecords, historySource: 'props' });
|
|
31
31
|
} else {
|
|
32
|
-
dataSourceService.add(value);
|
|
32
|
+
dataSourceService.add(value, { historySource: 'props' });
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
editDialog.value?.hide();
|
package/src/hooks/use-stage.ts
CHANGED
|
@@ -130,16 +130,16 @@ export const useStage = (stageOptions: StageOptions) => {
|
|
|
130
130
|
});
|
|
131
131
|
if (configs.length === 0) return;
|
|
132
132
|
|
|
133
|
-
editorService.update(configs, { changeRecordList });
|
|
133
|
+
editorService.update(configs, { changeRecordList, historySource: 'stage' });
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
stage.on('sort', (ev: SortEventData) => {
|
|
137
|
-
editorService.sort(ev.src, ev.dist);
|
|
137
|
+
editorService.sort(ev.src, ev.dist, { historySource: 'stage' });
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
stage.on('remove', (ev: RemoveEventData) => {
|
|
141
141
|
const nodes = ev.data.map(({ el }) => editorService.getNodeById(getIdFromEl()(el) || ''));
|
|
142
|
-
editorService.remove(nodes.filter((node) => Boolean(node)) as MNode[]);
|
|
142
|
+
editorService.remove(nodes.filter((node) => Boolean(node)) as MNode[], { historySource: 'stage' });
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
stage.on('select-parent', () => {
|
package/src/layouts/NavMenu.vue
CHANGED
|
@@ -80,7 +80,7 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => {
|
|
|
80
80
|
disabled: () => editorService.get('node')?.type === NodeType.PAGE,
|
|
81
81
|
handler: () => {
|
|
82
82
|
const node = editorService.get('node');
|
|
83
|
-
node && editorService.remove(node);
|
|
83
|
+
node && editorService.remove(node, { historySource: 'toolbar' });
|
|
84
84
|
},
|
|
85
85
|
});
|
|
86
86
|
break;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
:merged="group.steps.length > 1"
|
|
16
16
|
:op-type="group.opType"
|
|
17
17
|
:desc="describeGroup(group)"
|
|
18
|
+
:source="groupSource(group)"
|
|
18
19
|
:time="formatHistoryTime(groupTimestamp(group))"
|
|
19
20
|
:time-title="formatHistoryFullTime(groupTimestamp(group))"
|
|
20
21
|
:step-count="group.steps.length"
|
|
@@ -25,7 +26,8 @@
|
|
|
25
26
|
isCurrent: s.isCurrent,
|
|
26
27
|
desc: describeStep(s.step),
|
|
27
28
|
diffable: isStepDiffable ? isStepDiffable(s.step) : false,
|
|
28
|
-
revertable: s.applied,
|
|
29
|
+
revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
|
|
30
|
+
source: s.step.source,
|
|
29
31
|
time: formatHistoryTime(s.step.timestamp),
|
|
30
32
|
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
|
31
33
|
}))
|
|
@@ -58,7 +60,7 @@ import { computed } from 'vue';
|
|
|
58
60
|
|
|
59
61
|
import type { HistoryOpType } from '@editor/type';
|
|
60
62
|
|
|
61
|
-
import { formatHistoryFullTime, formatHistoryTime, groupTimestamp } from './composables';
|
|
63
|
+
import { formatHistoryFullTime, formatHistoryTime, groupSource, groupTimestamp } from './composables';
|
|
62
64
|
import GroupRow from './GroupRow.vue';
|
|
63
65
|
import InitialRow from './InitialRow.vue';
|
|
64
66
|
|
|
@@ -92,6 +94,8 @@ const props = withDefaults(
|
|
|
92
94
|
describeStep: (_step: any) => string;
|
|
93
95
|
/** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入;不传则一律不展示差异入口。 */
|
|
94
96
|
isStepDiffable?: (_step: any) => boolean;
|
|
97
|
+
/** 判断某个 step 是否支持回滚(如更新需带 changeRecords)。由父组件按业务类型注入;不传则已应用即可回滚。 */
|
|
98
|
+
isStepRevertable?: (_step: any) => boolean;
|
|
95
99
|
/** 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护以便跨 tab 复用。 */
|
|
96
100
|
expanded: Record<string, boolean>;
|
|
97
101
|
/** 是否支持「跳转到该记录」(goto)。默认 true。 */
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
:describe-group="describeGroup"
|
|
12
12
|
:describe-step="describeStep"
|
|
13
13
|
:is-step-diffable="isStepDiffable"
|
|
14
|
+
:is-step-revertable="isStepRevertable"
|
|
14
15
|
:expanded="expanded"
|
|
15
16
|
:goto-enabled="gotoEnabled"
|
|
16
17
|
@toggle="(key: string) => $emit('toggle', key)"
|
|
@@ -48,6 +49,8 @@ withDefaults(
|
|
|
48
49
|
describeStep: (_step: any) => string;
|
|
49
50
|
/** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入。 */
|
|
50
51
|
isStepDiffable: (_step: any) => boolean;
|
|
52
|
+
/** 判断某个 step 是否支持回滚(如更新需带 changeRecords)。由父组件按业务类型注入;不传则已应用即可回滚。 */
|
|
53
|
+
isStepRevertable?: (_step: any) => boolean;
|
|
51
54
|
/**
|
|
52
55
|
* 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。
|
|
53
56
|
* 本 tab 使用 `${prefix}-${id}-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
|
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
<span class="m-editor-history-list-item-op" :class="`op-${opType}`">{{ opLabel(opType) }}</span>
|
|
14
14
|
<span class="m-editor-history-list-item-desc">{{ desc }}</span>
|
|
15
15
|
|
|
16
|
-
<span
|
|
16
|
+
<span
|
|
17
|
+
v-if="!merged && sourceLabel(source)"
|
|
18
|
+
class="m-editor-history-list-item-source"
|
|
19
|
+
:title="`操作途径:${sourceLabel(source)}`"
|
|
20
|
+
>{{ sourceLabel(source) }}</span
|
|
21
|
+
>
|
|
22
|
+
|
|
23
|
+
<span v-if="!merged && time" class="m-editor-history-list-item-time" :title="timeTitle || time">{{ time }}</span>
|
|
17
24
|
|
|
18
25
|
<span v-if="merged" class="m-editor-history-list-item-merge">合并 {{ stepCount }} 步</span>
|
|
19
26
|
|
|
@@ -50,6 +57,12 @@
|
|
|
50
57
|
>
|
|
51
58
|
<span class="m-editor-history-list-item-index">#{{ s.index + 1 }}</span>
|
|
52
59
|
<span class="m-editor-history-list-substep-desc">{{ s.desc }}</span>
|
|
60
|
+
<span
|
|
61
|
+
v-if="sourceLabel(s.source)"
|
|
62
|
+
class="m-editor-history-list-item-source"
|
|
63
|
+
:title="`操作途径:${sourceLabel(s.source)}`"
|
|
64
|
+
>{{ sourceLabel(s.source) }}</span
|
|
65
|
+
>
|
|
53
66
|
<span v-if="s.time" class="m-editor-history-list-item-time" :title="s.timeTitle || s.time">{{ s.time }}</span>
|
|
54
67
|
<span
|
|
55
68
|
v-if="s.revertable"
|
|
@@ -80,9 +93,9 @@
|
|
|
80
93
|
<script lang="ts" setup>
|
|
81
94
|
import { computed } from 'vue';
|
|
82
95
|
|
|
83
|
-
import type { HistoryOpType } from '@editor/type';
|
|
96
|
+
import type { HistoryOpSource, HistoryOpType } from '@editor/type';
|
|
84
97
|
|
|
85
|
-
import { opLabel } from './composables';
|
|
98
|
+
import { opLabel, sourceLabel } from './composables';
|
|
86
99
|
|
|
87
100
|
defineOptions({
|
|
88
101
|
name: 'MEditorHistoryListGroupRow',
|
|
@@ -100,6 +113,8 @@ const props = withDefaults(
|
|
|
100
113
|
opType: HistoryOpType;
|
|
101
114
|
/** 组的整体描述文案,由上层根据 step / group 计算后传入,例如 "修改 button · style.color"。 */
|
|
102
115
|
desc: string;
|
|
116
|
+
/** 组的操作途径(一般取组内最近一步),用于头部展示「画布 / 树面板 / 配置面板…」标签。 */
|
|
117
|
+
source?: HistoryOpSource;
|
|
103
118
|
/** 组头部展示的时间文案(一般为组内最近一步的时间),为空时不渲染。 */
|
|
104
119
|
time?: string;
|
|
105
120
|
/** 组头部时间的 title 悬浮提示(完整时间),缺省时回退为 time。 */
|
|
@@ -115,6 +130,8 @@ const props = withDefaults(
|
|
|
115
130
|
diffable?: boolean;
|
|
116
131
|
/** 是否可对该子步执行「回滚」(已应用 + 业务侧确认支持反向)。父级根据 step 与 applied 决定。 */
|
|
117
132
|
revertable?: boolean;
|
|
133
|
+
/** 该子步的操作途径,用于展示「画布 / 树面板 / 配置面板…」标签。 */
|
|
134
|
+
source?: HistoryOpSource;
|
|
118
135
|
/** 该子步的时间文案,为空时不渲染。 */
|
|
119
136
|
time?: string;
|
|
120
137
|
/** 该子步时间的 title 悬浮提示(完整时间),缺省时回退为 time。 */
|
|
@@ -193,7 +193,8 @@ const targetText = computed(() => {
|
|
|
193
193
|
'data-source': '数据源',
|
|
194
194
|
'code-block': '代码块',
|
|
195
195
|
};
|
|
196
|
-
const
|
|
196
|
+
const { category } = payload.value;
|
|
197
|
+
const prefix = category ? categoryText[category] : '';
|
|
197
198
|
const label = payload.value.targetLabel || payload.value.type || '';
|
|
198
199
|
const { id } = payload.value;
|
|
199
200
|
const labelWithId = id !== undefined && id !== '' ? `${label}(${id})` : label;
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
:describe-group="describeDataSourceGroup"
|
|
45
45
|
:describe-step="describeDataSourceStep"
|
|
46
46
|
:is-step-diffable="isDataSourceStepDiffable"
|
|
47
|
+
:is-step-revertable="isDataSourceStepRevertable"
|
|
47
48
|
@toggle="toggleGroup"
|
|
48
49
|
@goto="onDataSourceGoto"
|
|
49
50
|
@goto-initial="onDataSourceGotoInitial"
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
:describe-group="describeCodeBlockGroup"
|
|
66
67
|
:describe-step="describeCodeBlockStep"
|
|
67
68
|
:is-step-diffable="isCodeBlockStepDiffable"
|
|
69
|
+
:is-step-revertable="isCodeBlockStepRevertable"
|
|
68
70
|
@toggle="toggleGroup"
|
|
69
71
|
@goto="onCodeBlockGoto"
|
|
70
72
|
@goto-initial="onCodeBlockGotoInitial"
|
|
@@ -141,6 +143,8 @@ import {
|
|
|
141
143
|
describeCodeBlockStep,
|
|
142
144
|
describeDataSourceGroup,
|
|
143
145
|
describeDataSourceStep,
|
|
146
|
+
isCodeBlockStepRevertable,
|
|
147
|
+
isDataSourceStepRevertable,
|
|
144
148
|
useHistoryList,
|
|
145
149
|
} from './composables';
|
|
146
150
|
import HistoryDiffDialog from './HistoryDiffDialog.vue';
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
:merged="group.steps.length > 1"
|
|
11
11
|
:op-type="group.opType"
|
|
12
12
|
:desc="describePageGroup(group)"
|
|
13
|
+
:source="groupSource(group)"
|
|
13
14
|
:time="formatHistoryTime(groupTimestamp(group))"
|
|
14
15
|
:time-title="formatHistoryFullTime(groupTimestamp(group))"
|
|
15
16
|
:step-count="group.steps.length"
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
isCurrent: s.isCurrent,
|
|
21
22
|
desc: describePageStep(s.step),
|
|
22
23
|
diffable: isPageStepDiffable(s.step),
|
|
23
|
-
revertable: s.applied,
|
|
24
|
+
revertable: s.applied && isPageStepRevertable(s.step),
|
|
25
|
+
source: s.step.source,
|
|
24
26
|
time: formatHistoryTime(s.step.timestamp),
|
|
25
27
|
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
|
26
28
|
}))
|
|
@@ -53,7 +55,9 @@ import {
|
|
|
53
55
|
describePageStep,
|
|
54
56
|
formatHistoryFullTime,
|
|
55
57
|
formatHistoryTime,
|
|
58
|
+
groupSource,
|
|
56
59
|
groupTimestamp,
|
|
60
|
+
isPageStepRevertable,
|
|
57
61
|
} from './composables';
|
|
58
62
|
import GroupRow from './GroupRow.vue';
|
|
59
63
|
import InitialRow from './InitialRow.vue';
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
CodeBlockStepValue,
|
|
9
9
|
DataSourceHistoryGroup,
|
|
10
10
|
DataSourceStepValue,
|
|
11
|
+
HistoryOpSource,
|
|
11
12
|
HistoryOpType,
|
|
12
13
|
PageHistoryGroup,
|
|
13
14
|
StepValue,
|
|
@@ -107,6 +108,32 @@ export const opLabel = (op: HistoryOpType) => {
|
|
|
107
108
|
}
|
|
108
109
|
};
|
|
109
110
|
|
|
111
|
+
/** 内置操作途径的中文文案;自定义来源直接回显原值,未知 / 缺省返回空串(UI 据此不渲染)。 */
|
|
112
|
+
const HISTORY_SOURCE_LABELS: Record<string, string> = {
|
|
113
|
+
stage: '画布',
|
|
114
|
+
tree: '树面板',
|
|
115
|
+
'component-panel': '组件面板',
|
|
116
|
+
props: '配置面板',
|
|
117
|
+
code: '源码',
|
|
118
|
+
'stage-contextmenu': '画布菜单',
|
|
119
|
+
'tree-contextmenu': '树菜单',
|
|
120
|
+
toolbar: '工具栏',
|
|
121
|
+
shortcut: '快捷键',
|
|
122
|
+
rollback: '回滚',
|
|
123
|
+
api: '接口',
|
|
124
|
+
ai: 'AI',
|
|
125
|
+
unknown: '未知',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
|
|
129
|
+
export const sourceLabel = (source: HistoryOpSource = 'unknown'): string => {
|
|
130
|
+
return HISTORY_SOURCE_LABELS[source] ?? `${source}`;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
|
|
134
|
+
export const groupSource = (group: { steps: { step: { source?: HistoryOpSource } }[] }): HistoryOpSource | undefined =>
|
|
135
|
+
group.steps[group.steps.length - 1]?.step.source;
|
|
136
|
+
|
|
110
137
|
const nameOf = (node: { name?: string; id?: string | number; type?: string }) =>
|
|
111
138
|
node?.name || node?.type || `${node?.id ?? ''}`;
|
|
112
139
|
|
|
@@ -224,3 +251,36 @@ export const describeCodeBlockGroup = (group: CodeBlockHistoryGroup) => {
|
|
|
224
251
|
const target = labelWithId(rawName, group.id);
|
|
225
252
|
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
226
253
|
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 页面 step 是否支持「回滚」(类 git revert):
|
|
257
|
+
* - 新增 / 删除:不依赖 changeRecords,反向应用即删除 / 加回,始终可回滚;
|
|
258
|
+
* - 更新:必须每个被更新节点都带有 changeRecords,才支持按 propPath 局部反向 patch。
|
|
259
|
+
* 缺失 changeRecords 的更新只能整节点替换,会冲掉该节点后续的无关变更,因此不支持回滚。
|
|
260
|
+
*/
|
|
261
|
+
export const isPageStepRevertable = (step: StepValue): boolean => {
|
|
262
|
+
if (step.opType !== 'update') return true;
|
|
263
|
+
const items = step.updatedItems ?? [];
|
|
264
|
+
if (!items.length) return false;
|
|
265
|
+
return items.every((item) => Boolean(item.changeRecords?.length));
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 数据源 step 是否支持「回滚」:
|
|
270
|
+
* - 新增(oldSchema=null)/ 删除(newSchema=null):不依赖 changeRecords,始终可回滚;
|
|
271
|
+
* - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
272
|
+
*/
|
|
273
|
+
export const isDataSourceStepRevertable = (step: DataSourceStepValue): boolean => {
|
|
274
|
+
if (step.oldSchema === null || step.newSchema === null) return true;
|
|
275
|
+
return Boolean(step.changeRecords?.length);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 代码块 step 是否支持「回滚」:
|
|
280
|
+
* - 新增(oldContent=null)/ 删除(newContent=null):不依赖 changeRecords,始终可回滚;
|
|
281
|
+
* - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
282
|
+
*/
|
|
283
|
+
export const isCodeBlockStepRevertable = (step: CodeBlockStepValue): boolean => {
|
|
284
|
+
if (step.oldContent === null || step.newContent === null) return true;
|
|
285
|
+
return Boolean(step.changeRecords?.length);
|
|
286
|
+
};
|
|
@@ -151,7 +151,11 @@ const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
// 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
|
|
155
|
+
// 源码编辑器(CodeEditor @save → saveCode)保存时不带 eventData,据此标记为「源码编辑器」。
|
|
156
|
+
const historySource = eventData ? 'props' : 'code';
|
|
157
|
+
|
|
158
|
+
editorService.update(newValue, { changeRecords: eventData?.changeRecords, historySource });
|
|
155
159
|
} catch (e: any) {
|
|
156
160
|
emit('submit-error', e);
|
|
157
161
|
}
|
|
@@ -94,11 +94,15 @@ let clientX: number;
|
|
|
94
94
|
let clientY: number;
|
|
95
95
|
|
|
96
96
|
const appendComponent = ({ text, type, data = {} }: ComponentItem): void => {
|
|
97
|
-
editorService.add(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
editorService.add(
|
|
98
|
+
{
|
|
99
|
+
name: text,
|
|
100
|
+
type,
|
|
101
|
+
...data,
|
|
102
|
+
},
|
|
103
|
+
undefined,
|
|
104
|
+
{ historySource: 'component-panel' },
|
|
105
|
+
);
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
const dragstartHandler = ({ text, type, data = {} }: ComponentItem, e: DragEvent) => {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.key}`)"></Icon>
|
|
25
25
|
</TMagicTooltip>
|
|
26
26
|
<TMagicTooltip v-if="data.type === 'code' && editable" effect="dark" content="删除" placement="bottom">
|
|
27
|
-
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.key}
|
|
27
|
+
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.key}`, { historySource: 'tree' })"></Icon>
|
|
28
28
|
</TMagicTooltip>
|
|
29
29
|
<slot name="code-block-panel-tool" :id="data.key" :data="data"></slot>
|
|
30
30
|
</template>
|
|
@@ -44,7 +44,7 @@ import Tree from '@editor/components/Tree.vue';
|
|
|
44
44
|
import { useFilter } from '@editor/hooks/use-filter';
|
|
45
45
|
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
|
46
46
|
import { useServices } from '@editor/hooks/use-services';
|
|
47
|
-
import { type CodeBlockListSlots, CodeDeleteErrorType, type TreeNodeData } from '@editor/type';
|
|
47
|
+
import { type CodeBlockListSlots, CodeDeleteErrorType, HistoryOpSource, type TreeNodeData } from '@editor/type';
|
|
48
48
|
|
|
49
49
|
defineSlots<CodeBlockListSlots>();
|
|
50
50
|
|
|
@@ -60,7 +60,7 @@ const props = defineProps<{
|
|
|
60
60
|
|
|
61
61
|
const emit = defineEmits<{
|
|
62
62
|
edit: [id: string];
|
|
63
|
-
remove: [id: string];
|
|
63
|
+
remove: [id: string, { historySource?: HistoryOpSource }];
|
|
64
64
|
'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
|
|
65
65
|
}>();
|
|
66
66
|
|
|
@@ -142,7 +142,7 @@ const editCode = (id: string) => {
|
|
|
142
142
|
emit('edit', id);
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
-
const deleteCode = async (id: string) => {
|
|
145
|
+
const deleteCode = async (id: string, { historySource }: { historySource?: HistoryOpSource } = {}) => {
|
|
146
146
|
const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
|
|
147
147
|
const existBinds = Boolean(currentCode?.items?.length);
|
|
148
148
|
const undeleteableList = codeBlockService.getUndeletableList() || [];
|
|
@@ -154,7 +154,7 @@ const deleteCode = async (id: string) => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
// 无绑定关系,且不在不可删除列表中
|
|
157
|
-
emit('remove', id);
|
|
157
|
+
emit('remove', id, { historySource });
|
|
158
158
|
} else {
|
|
159
159
|
if (typeof props.customError === 'function') {
|
|
160
160
|
props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
|
|
@@ -122,7 +122,7 @@ const {
|
|
|
122
122
|
menuData: contentMenuData,
|
|
123
123
|
contentMenuHideHandler,
|
|
124
124
|
} = useContentMenu((id: string) => {
|
|
125
|
-
codeBlockListRef.value?.deleteCode(id);
|
|
125
|
+
codeBlockListRef.value?.deleteCode(id, { historySource: 'tree-contextmenu' });
|
|
126
126
|
});
|
|
127
127
|
const menuData = computed<(MenuButton | MenuComponent)[]>(() => props.customContentMenu(contentMenuData, 'code-block'));
|
|
128
128
|
</script>
|
|
@@ -41,7 +41,7 @@ export const useContentMenu = (deleteCode: (id: string) => void) => {
|
|
|
41
41
|
|
|
42
42
|
const newCodeId = await codeBlockService.getUniqueId();
|
|
43
43
|
|
|
44
|
-
codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock));
|
|
44
|
+
codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock), { historySource: 'tree-contextmenu' });
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
{
|
|
@@ -129,7 +129,7 @@ const removeHandler = async (id: string) => {
|
|
|
129
129
|
type: 'warning',
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
dataSourceService.remove(id);
|
|
132
|
+
dataSourceService.remove(id, { historySource: 'tree-contextmenu' });
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
const dataSourceListRef = useTemplateRef<InstanceType<typeof DataSourceList>>('dataSourceList');
|
|
@@ -41,11 +41,15 @@ const createMenuItems = (group: ComponentGroup): MenuButton[] =>
|
|
|
41
41
|
type: 'button',
|
|
42
42
|
icon: component.icon,
|
|
43
43
|
handler: () => {
|
|
44
|
-
editorService.add(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
editorService.add(
|
|
45
|
+
{
|
|
46
|
+
name: component.text,
|
|
47
|
+
type: component.type,
|
|
48
|
+
...(component.data || {}),
|
|
49
|
+
},
|
|
50
|
+
undefined,
|
|
51
|
+
{ historySource: 'tree-contextmenu' },
|
|
52
|
+
);
|
|
49
53
|
},
|
|
50
54
|
}));
|
|
51
55
|
|
|
@@ -57,9 +61,13 @@ const getSubMenuData = computed<MenuButton[]>(() => {
|
|
|
57
61
|
type: 'button',
|
|
58
62
|
icon: Files,
|
|
59
63
|
handler: () => {
|
|
60
|
-
editorService.add(
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
editorService.add(
|
|
65
|
+
{
|
|
66
|
+
type: 'tab-pane',
|
|
67
|
+
},
|
|
68
|
+
undefined,
|
|
69
|
+
{ historySource: 'tree-contextmenu' },
|
|
70
|
+
);
|
|
63
71
|
},
|
|
64
72
|
},
|
|
65
73
|
];
|
|
@@ -106,9 +114,9 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
106
114
|
items: getSubMenuData.value,
|
|
107
115
|
},
|
|
108
116
|
useCopyMenu(),
|
|
109
|
-
usePasteMenu(),
|
|
110
|
-
useDeleteMenu(),
|
|
111
|
-
useMoveToMenu(services),
|
|
117
|
+
usePasteMenu('tree-contextmenu'),
|
|
118
|
+
useDeleteMenu('tree-contextmenu'),
|
|
119
|
+
useMoveToMenu(services, 'tree-contextmenu'),
|
|
112
120
|
...props.layerContentMenu,
|
|
113
121
|
],
|
|
114
122
|
'layer',
|
|
@@ -25,9 +25,12 @@ const props = defineProps<{
|
|
|
25
25
|
const { editorService } = useServices();
|
|
26
26
|
|
|
27
27
|
const setNodeVisible = (visible: boolean) => {
|
|
28
|
-
editorService.update(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
editorService.update(
|
|
29
|
+
{
|
|
30
|
+
id: props.data.id,
|
|
31
|
+
visible,
|
|
32
|
+
},
|
|
33
|
+
{ historySource: 'tree' },
|
|
34
|
+
);
|
|
32
35
|
};
|
|
33
36
|
</script>
|
|
@@ -49,11 +49,11 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
49
49
|
display: () => canCenter.value,
|
|
50
50
|
handler: () => {
|
|
51
51
|
if (!nodes.value) return;
|
|
52
|
-
editorService.alignCenter(nodes.value);
|
|
52
|
+
editorService.alignCenter(nodes.value, { historySource: 'stage-contextmenu' });
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
useCopyMenu(),
|
|
56
|
-
usePasteMenu(menuRef),
|
|
56
|
+
usePasteMenu('stage-contextmenu', menuRef),
|
|
57
57
|
{
|
|
58
58
|
type: 'divider',
|
|
59
59
|
direction: 'horizontal',
|
|
@@ -68,7 +68,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
68
68
|
icon: markRaw(Top),
|
|
69
69
|
display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
|
|
70
70
|
handler: () => {
|
|
71
|
-
editorService.moveLayer(1);
|
|
71
|
+
editorService.moveLayer(1, { historySource: 'stage-contextmenu' });
|
|
72
72
|
},
|
|
73
73
|
},
|
|
74
74
|
{
|
|
@@ -77,7 +77,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
77
77
|
icon: markRaw(Bottom),
|
|
78
78
|
display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
|
|
79
79
|
handler: () => {
|
|
80
|
-
editorService.moveLayer(-1);
|
|
80
|
+
editorService.moveLayer(-1, { historySource: 'stage-contextmenu' });
|
|
81
81
|
},
|
|
82
82
|
},
|
|
83
83
|
{
|
|
@@ -86,7 +86,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
86
86
|
icon: markRaw(Top),
|
|
87
87
|
display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
|
|
88
88
|
handler: () => {
|
|
89
|
-
editorService.moveLayer(LayerOffset.TOP);
|
|
89
|
+
editorService.moveLayer(LayerOffset.TOP, { historySource: 'stage-contextmenu' });
|
|
90
90
|
},
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -95,16 +95,16 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
|
|
|
95
95
|
icon: markRaw(Bottom),
|
|
96
96
|
display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
|
|
97
97
|
handler: () => {
|
|
98
|
-
editorService.moveLayer(LayerOffset.BOTTOM);
|
|
98
|
+
editorService.moveLayer(LayerOffset.BOTTOM, { historySource: 'stage-contextmenu' });
|
|
99
99
|
},
|
|
100
100
|
},
|
|
101
|
-
useMoveToMenu(services),
|
|
101
|
+
useMoveToMenu(services, 'stage-contextmenu'),
|
|
102
102
|
{
|
|
103
103
|
type: 'divider',
|
|
104
104
|
direction: 'horizontal',
|
|
105
105
|
display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect,
|
|
106
106
|
},
|
|
107
|
-
useDeleteMenu(),
|
|
107
|
+
useDeleteMenu('stage-contextmenu'),
|
|
108
108
|
{
|
|
109
109
|
type: 'divider',
|
|
110
110
|
direction: 'horizontal',
|