@tmagic/editor 1.8.0-beta.5 → 1.8.0-beta.7
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/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +25 -8
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +25 -12
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +27 -5
- package/dist/es/index.js +2 -2
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +10 -14
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +12 -4
- package/dist/es/layouts/history-list/composables.js +28 -77
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +16 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +23 -1
- package/dist/es/services/codeBlock.js +30 -19
- package/dist/es/services/dataSource.js +29 -21
- package/dist/es/services/editor.js +52 -33
- package/dist/es/services/history.js +10 -15
- package/dist/es/style.css +4 -1
- package/dist/es/utils/data-source/index.js +2 -0
- package/dist/es/utils/editor.js +68 -48
- package/dist/es/utils/history.js +42 -33
- package/dist/style.css +4 -1
- package/dist/tmagic-editor.umd.cjs +412 -295
- package/package.json +7 -7
- package/src/fields/CodeSelectCol.vue +7 -1
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
- package/src/fields/DataSourceFields.vue +37 -8
- package/src/fields/DataSourceMethodSelect.vue +9 -4
- package/src/fields/DataSourceMethods.vue +42 -21
- package/src/fields/StyleSetter/components/Border.vue +15 -6
- package/src/layouts/history-list/HistoryListPanel.vue +12 -25
- package/src/layouts/history-list/InitialRow.vue +3 -0
- package/src/layouts/history-list/composables.ts +34 -94
- package/src/layouts/sidebar/Sidebar.vue +2 -2
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +31 -2
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +26 -1
- package/src/services/codeBlock.ts +28 -22
- package/src/services/dataSource.ts +29 -24
- package/src/services/editor.ts +41 -37
- package/src/services/history.ts +19 -25
- package/src/theme/style-setter/border.scss +4 -1
- package/src/type.ts +38 -26
- package/src/utils/data-source/index.ts +2 -0
- package/src/utils/editor.ts +125 -59
- package/src/utils/history.ts +48 -45
- package/types/index.d.ts +109 -112
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.7",
|
|
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/form": "1.8.0-beta.
|
|
62
|
-
"@tmagic/
|
|
63
|
-
"@tmagic/
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/stage": "1.8.0-beta.
|
|
61
|
+
"@tmagic/form": "1.8.0-beta.7",
|
|
62
|
+
"@tmagic/design": "1.8.0-beta.7",
|
|
63
|
+
"@tmagic/table": "1.8.0-beta.7",
|
|
64
|
+
"@tmagic/utils": "1.8.0-beta.7",
|
|
65
|
+
"@tmagic/stage": "1.8.0-beta.7"
|
|
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.7"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"typescript": {
|
|
@@ -107,10 +107,12 @@ const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues
|
|
|
107
107
|
|
|
108
108
|
const notEditable = computed(() => filterFunction(mForm, props.config.notEditable, props));
|
|
109
109
|
|
|
110
|
-
const
|
|
110
|
+
const codeBlockSidePanel = computed(() =>
|
|
111
111
|
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.CODE_BLOCK),
|
|
112
112
|
);
|
|
113
113
|
|
|
114
|
+
const hasCodeBlockSidePanel = computed(() => codeBlockSidePanel.value);
|
|
115
|
+
|
|
114
116
|
/**
|
|
115
117
|
* 根据代码块id获取代码块参数配置
|
|
116
118
|
* @param codeId 代码块ID
|
|
@@ -191,6 +193,10 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
|
|
191
193
|
};
|
|
192
194
|
|
|
193
195
|
const editCode = (id: string) => {
|
|
196
|
+
const sideBarItem = codeBlockSidePanel.value;
|
|
197
|
+
if (sideBarItem) {
|
|
198
|
+
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.CODE_BLOCK);
|
|
199
|
+
}
|
|
194
200
|
eventBus?.emit('edit-code', id);
|
|
195
201
|
};
|
|
196
202
|
</script>
|
|
@@ -211,11 +211,25 @@ const onChangeHandler = (v: string[] = []) => {
|
|
|
211
211
|
emit('change', v);
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
-
const
|
|
214
|
+
const dataSourceSidePanel = computed(() =>
|
|
215
215
|
uiService.get('sideBarItems').find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
|
216
216
|
);
|
|
217
217
|
|
|
218
|
+
const hasDataSourceSidePanel = computed(() => dataSourceSidePanel.value);
|
|
219
|
+
|
|
218
220
|
const editHandler = (id: string) => {
|
|
219
|
-
|
|
221
|
+
const sideBarItem = dataSourceSidePanel.value;
|
|
222
|
+
if (sideBarItem) {
|
|
223
|
+
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const dataSourceId = removeDataSourceFieldPrefix(id);
|
|
227
|
+
const fieldPath = selectFieldsId.value;
|
|
228
|
+
|
|
229
|
+
if (fieldPath.length) {
|
|
230
|
+
eventBus?.emit('edit-data-source-field', dataSourceId, [...fieldPath]);
|
|
231
|
+
} else {
|
|
232
|
+
eventBus?.emit('edit-data-source', dataSourceId);
|
|
233
|
+
}
|
|
220
234
|
};
|
|
221
235
|
</script>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script setup lang="ts">
|
|
50
|
-
import { computed, inject, Ref, ref } from 'vue';
|
|
50
|
+
import { computed, type ComputedRef, inject, onMounted, provide, Ref, ref } from 'vue';
|
|
51
51
|
|
|
52
52
|
import type { DataSchema } from '@tmagic/core';
|
|
53
53
|
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
|
@@ -101,6 +101,16 @@ const newHandler = () => {
|
|
|
101
101
|
addDialogVisible.value = true;
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
const editField = (row: Record<string, any>, index: number) => {
|
|
105
|
+
fieldValues.value = {
|
|
106
|
+
...row,
|
|
107
|
+
index,
|
|
108
|
+
};
|
|
109
|
+
fieldTitle.value = `编辑${row.title}`;
|
|
110
|
+
calcBoxPosition();
|
|
111
|
+
addDialogVisible.value = true;
|
|
112
|
+
};
|
|
113
|
+
|
|
104
114
|
const fieldChange = ({ index, ...value }: Record<string, any>, data: ContainerChangeEventData) => {
|
|
105
115
|
addDialogVisible.value = false;
|
|
106
116
|
|
|
@@ -158,13 +168,7 @@ const fieldColumns: ColumnConfig[] = [
|
|
|
158
168
|
{
|
|
159
169
|
text: '编辑',
|
|
160
170
|
handler: (row: Record<string, any>, index: number) => {
|
|
161
|
-
|
|
162
|
-
...row,
|
|
163
|
-
index,
|
|
164
|
-
};
|
|
165
|
-
fieldTitle.value = `编辑${row.title}`;
|
|
166
|
-
calcBoxPosition();
|
|
167
|
-
addDialogVisible.value = true;
|
|
171
|
+
editField(row, index);
|
|
168
172
|
},
|
|
169
173
|
},
|
|
170
174
|
{
|
|
@@ -354,4 +358,29 @@ const { height: editorHeight } = useEditorContentHeight();
|
|
|
354
358
|
|
|
355
359
|
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
|
356
360
|
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
|
|
364
|
+
* 当前层消费 path[0],并把剩余路径下发给嵌套字段,实现逐层打开。
|
|
365
|
+
*/
|
|
366
|
+
const editingFieldPath = inject<ComputedRef<string[]>>(
|
|
367
|
+
'editingDataSourceFieldPath',
|
|
368
|
+
computed(() => []),
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
provide(
|
|
372
|
+
'editingDataSourceFieldPath',
|
|
373
|
+
computed(() => editingFieldPath.value.slice(1)),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
onMounted(() => {
|
|
377
|
+
const path = editingFieldPath.value;
|
|
378
|
+
if (!path.length) return;
|
|
379
|
+
|
|
380
|
+
const fields: Record<string, any>[] = props.model[props.name] || [];
|
|
381
|
+
const index = fields.findIndex((field) => field.name === path[0]);
|
|
382
|
+
if (index === -1) return;
|
|
383
|
+
|
|
384
|
+
editField(fields[index], index);
|
|
385
|
+
});
|
|
357
386
|
</script>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
></MCascader>
|
|
14
14
|
|
|
15
15
|
<TMagicTooltip
|
|
16
|
-
v-if="model[name] && isCustomMethod &&
|
|
16
|
+
v-if="model[name] && isCustomMethod && dataSourceSidePanel && !isCompare"
|
|
17
17
|
:content="notEditable ? '查看' : '编辑'"
|
|
18
18
|
>
|
|
19
19
|
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
|
|
@@ -75,7 +75,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodSelectConfig>>
|
|
|
75
75
|
disabled: false,
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
const
|
|
78
|
+
const dataSourceSidePanel = computed(() =>
|
|
79
79
|
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
|
80
80
|
);
|
|
81
81
|
|
|
@@ -208,12 +208,17 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
const editCodeHandler = () => {
|
|
211
|
-
const [id] = props.model[props.name];
|
|
211
|
+
const [id, methodName] = props.model[props.name];
|
|
212
212
|
|
|
213
213
|
const dataSource = dataSourceService.getDataSourceById(id);
|
|
214
214
|
|
|
215
215
|
if (!dataSource) return;
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
const sideBarItem = dataSourceSidePanel.value;
|
|
218
|
+
if (sideBarItem) {
|
|
219
|
+
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
eventBus?.emit('edit-data-source-method', id, methodName);
|
|
218
223
|
};
|
|
219
224
|
</script>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</template>
|
|
22
22
|
|
|
23
23
|
<script setup lang="ts">
|
|
24
|
-
import { computed, inject, nextTick, ref, useTemplateRef } from 'vue';
|
|
24
|
+
import { computed, type ComputedRef, inject, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
|
25
25
|
import { cloneDeep } from 'lodash-es';
|
|
26
26
|
|
|
27
27
|
import type { CodeBlockContent } from '@tmagic/core';
|
|
@@ -52,6 +52,29 @@ const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>(
|
|
|
52
52
|
|
|
53
53
|
let editIndex = -1;
|
|
54
54
|
|
|
55
|
+
const editMethod = (method: CodeBlockContent, index: number) => {
|
|
56
|
+
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
|
57
|
+
|
|
58
|
+
if (method.content) {
|
|
59
|
+
if (typeof method.content !== 'string') {
|
|
60
|
+
codeContent = method.content.toString();
|
|
61
|
+
} else {
|
|
62
|
+
codeContent = method.content;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
codeConfig.value = {
|
|
67
|
+
...cloneDeep(method),
|
|
68
|
+
content: codeContent,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
editIndex = index;
|
|
72
|
+
|
|
73
|
+
nextTick(() => {
|
|
74
|
+
codeBlockEditorRef.value?.show();
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
55
78
|
const methodColumns: ColumnConfig[] = [
|
|
56
79
|
{
|
|
57
80
|
label: '名称',
|
|
@@ -77,26 +100,7 @@ const methodColumns: ColumnConfig[] = [
|
|
|
77
100
|
{
|
|
78
101
|
text: '编辑',
|
|
79
102
|
handler: (method: CodeBlockContent, index: number) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (method.content) {
|
|
83
|
-
if (typeof method.content !== 'string') {
|
|
84
|
-
codeContent = method.content.toString();
|
|
85
|
-
} else {
|
|
86
|
-
codeContent = method.content;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
codeConfig.value = {
|
|
91
|
-
...cloneDeep(method),
|
|
92
|
-
content: codeContent,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
editIndex = index;
|
|
96
|
-
|
|
97
|
-
nextTick(() => {
|
|
98
|
-
codeBlockEditorRef.value?.show();
|
|
99
|
-
});
|
|
103
|
+
editMethod(method, index);
|
|
100
104
|
},
|
|
101
105
|
},
|
|
102
106
|
{
|
|
@@ -158,4 +162,21 @@ const submitCodeHandler = (value: CodeBlockContent, data: ContainerChangeEventDa
|
|
|
158
162
|
|
|
159
163
|
codeBlockEditorRef.value?.hide();
|
|
160
164
|
};
|
|
165
|
+
|
|
166
|
+
/** 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的方法名 */
|
|
167
|
+
const editingMethodName = inject<ComputedRef<string | undefined>>(
|
|
168
|
+
'editingDataSourceMethodName',
|
|
169
|
+
computed(() => ''),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
onMounted(() => {
|
|
173
|
+
const methodName = editingMethodName.value;
|
|
174
|
+
if (!methodName) return;
|
|
175
|
+
|
|
176
|
+
const methods: CodeBlockContent[] = props.model[props.name] || [];
|
|
177
|
+
const index = methods.findIndex((method) => method.name === methodName);
|
|
178
|
+
if (index === -1) return;
|
|
179
|
+
|
|
180
|
+
editMethod(methods[index], index);
|
|
181
|
+
});
|
|
161
182
|
</script>
|
|
@@ -4,27 +4,31 @@
|
|
|
4
4
|
<div class="border-icon-container-row">
|
|
5
5
|
<div
|
|
6
6
|
class="border-icon border-icon-top"
|
|
7
|
-
:class="{ active: direction === 'Top' }"
|
|
7
|
+
:class="{ active: direction === 'Top', configured: isConfigured('Top') }"
|
|
8
8
|
@click="selectDirection('Top')"
|
|
9
9
|
></div>
|
|
10
10
|
</div>
|
|
11
11
|
<div class="border-icon-container-row">
|
|
12
12
|
<div
|
|
13
13
|
class="border-icon border-icon-left"
|
|
14
|
-
:class="{ active: direction === 'Left' }"
|
|
14
|
+
:class="{ active: direction === 'Left', configured: isConfigured('Left') }"
|
|
15
15
|
@click="selectDirection('Left')"
|
|
16
16
|
></div>
|
|
17
|
-
<div
|
|
17
|
+
<div
|
|
18
|
+
class="border-icon"
|
|
19
|
+
:class="{ active: direction === '', configured: isConfigured('') }"
|
|
20
|
+
@click="selectDirection()"
|
|
21
|
+
></div>
|
|
18
22
|
<div
|
|
19
23
|
class="border-icon border-icon-right"
|
|
20
|
-
:class="{ active: direction === 'Right' }"
|
|
24
|
+
:class="{ active: direction === 'Right', configured: isConfigured('Right') }"
|
|
21
25
|
@click="selectDirection('Right')"
|
|
22
26
|
></div>
|
|
23
27
|
</div>
|
|
24
28
|
<div class="border-icon-container-row">
|
|
25
29
|
<div
|
|
26
30
|
class="border-icon border-icon-bottom"
|
|
27
|
-
:class="{ active: direction === 'Bottom' }"
|
|
31
|
+
:class="{ active: direction === 'Bottom', configured: isConfigured('Bottom') }"
|
|
28
32
|
@click="selectDirection('Bottom')"
|
|
29
33
|
></div>
|
|
30
34
|
</div>
|
|
@@ -98,7 +102,7 @@ const emit = defineEmits<{
|
|
|
98
102
|
addDiffCount: [];
|
|
99
103
|
}>();
|
|
100
104
|
|
|
101
|
-
withDefaults(
|
|
105
|
+
const props = withDefaults(
|
|
102
106
|
defineProps<{
|
|
103
107
|
model: FormValue;
|
|
104
108
|
lastValues?: FormValue;
|
|
@@ -109,6 +113,11 @@ withDefaults(
|
|
|
109
113
|
{},
|
|
110
114
|
);
|
|
111
115
|
|
|
116
|
+
const hasValue = (value: unknown) => value !== undefined && value !== null && value !== '';
|
|
117
|
+
|
|
118
|
+
const isConfigured = (d: string) =>
|
|
119
|
+
['Width', 'Color', 'Style'].some((key) => hasValue(props.model?.[`border${d}${key}`]));
|
|
120
|
+
|
|
112
121
|
const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
|
|
113
122
|
eventData.changeRecords?.forEach((record) => {
|
|
114
123
|
emit('change', record.value, {
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
/**
|
|
101
101
|
* 历史记录面板:在顶部 NavMenu 上点击图标打开 popover,分三个 tab:
|
|
102
102
|
* - 页面:当前活动页面的历史栈,连续修改同一节点的多步会被合并成一组
|
|
103
|
-
* - 数据源:以 dataSource.id
|
|
104
|
-
* - 代码块:同上,按 codeBlock.id
|
|
103
|
+
* - 数据源:以 dataSource.id 分桶,每条操作记录独立展示
|
|
104
|
+
* - 代码块:同上,按 codeBlock.id 分桶,每条操作记录独立展示
|
|
105
105
|
*
|
|
106
106
|
* 数据通过 historyService 暴露的聚合 API 读取,UI 仅用于只读展示,
|
|
107
107
|
* 同时支持点击任意一条记录跳转至该状态:
|
|
@@ -135,6 +135,7 @@ import type { FormState } from '@tmagic/form';
|
|
|
135
135
|
import MIcon from '@editor/components/Icon.vue';
|
|
136
136
|
import { useServices } from '@editor/hooks/use-services';
|
|
137
137
|
import type {
|
|
138
|
+
BaseStepValue,
|
|
138
139
|
CodeBlockStepValue,
|
|
139
140
|
DataSourceStepValue,
|
|
140
141
|
DiffDialogPayload,
|
|
@@ -143,15 +144,7 @@ import type {
|
|
|
143
144
|
} from '@editor/type';
|
|
144
145
|
|
|
145
146
|
import BucketTab from './BucketTab.vue';
|
|
146
|
-
import {
|
|
147
|
-
describeCodeBlockGroup,
|
|
148
|
-
describeCodeBlockStep,
|
|
149
|
-
describeDataSourceGroup,
|
|
150
|
-
describeDataSourceStep,
|
|
151
|
-
isCodeBlockStepRevertable,
|
|
152
|
-
isDataSourceStepRevertable,
|
|
153
|
-
useHistoryList,
|
|
154
|
-
} from './composables';
|
|
147
|
+
import { describeStep, isSingleDiffStepRevertable, useHistoryList } from './composables';
|
|
155
148
|
import HistoryDiffDialog from './HistoryDiffDialog.vue';
|
|
156
149
|
import PageTab from './PageTab.vue';
|
|
157
150
|
|
|
@@ -223,34 +216,28 @@ const {
|
|
|
223
216
|
*/
|
|
224
217
|
const pageMarker = computed(() => historyService.getPageMarker());
|
|
225
218
|
|
|
226
|
-
/** 数据源 step 仅 update(前后 schema 都存在)时可查看差异。 */
|
|
227
|
-
const isDataSourceStepDiffable = (step: DataSourceStepValue) =>
|
|
228
|
-
Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
|
|
229
|
-
|
|
230
219
|
/** 代码块 step 仅 update(前后 content 都存在)时可查看差异。 */
|
|
231
|
-
const
|
|
232
|
-
Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
|
|
220
|
+
const isStepDiffable = (step: BaseStepValue) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
|
|
233
221
|
|
|
234
222
|
/**
|
|
235
223
|
* 数据源 / 代码块两类 bucket 历史的整体渲染配置:把 title / prefix 与各自的描述、
|
|
236
224
|
* 可差异、可回滚判定收敛为单一对象整体注入 BucketTab,组件内部按需读取。
|
|
237
225
|
*/
|
|
226
|
+
// 数据源/代码块不做相邻合并,每组恒为单步,省略 describeGroup,由 toRowGroup 回退到 describeStep。
|
|
238
227
|
const dataSourceConfig: HistoryBucketConfig<DataSourceStepValue> = {
|
|
239
228
|
title: '数据源',
|
|
240
229
|
prefix: 'ds',
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
isStepRevertable: isDataSourceStepRevertable,
|
|
230
|
+
describeStep: (step: DataSourceStepValue): string => describeStep(step, (schema) => schema?.title, '数据源'),
|
|
231
|
+
isStepDiffable,
|
|
232
|
+
isStepRevertable: isSingleDiffStepRevertable,
|
|
245
233
|
};
|
|
246
234
|
|
|
247
235
|
const codeBlockConfig: HistoryBucketConfig<CodeBlockStepValue> = {
|
|
248
236
|
title: '代码块',
|
|
249
237
|
prefix: 'cb',
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
isStepRevertable: isCodeBlockStepRevertable,
|
|
238
|
+
describeStep: (step: CodeBlockStepValue): string => describeStep(step, (content) => content?.name, '代码块'),
|
|
239
|
+
isStepDiffable,
|
|
240
|
+
isStepRevertable: isSingleDiffStepRevertable,
|
|
254
241
|
};
|
|
255
242
|
|
|
256
243
|
/** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<span class="m-editor-history-list-item-index" title="历史步骤编号 #0(未修改的初始状态)">#0</span>
|
|
8
8
|
<span class="m-editor-history-list-item-op op-initial">初始</span>
|
|
9
9
|
<span class="m-editor-history-list-item-desc">{{ desc }}</span>
|
|
10
|
+
<span v-if="saved" class="m-editor-history-list-item-saved" title="该记录为最近一次保存的状态">已保存</span>
|
|
10
11
|
<span v-if="gotoEnabled && !isCurrent" class="m-editor-history-list-item-actions">
|
|
11
12
|
<span class="m-editor-history-list-item-goto" title="回到该记录" @click.stop="onClick">回到</span>
|
|
12
13
|
</span>
|
|
@@ -49,6 +50,8 @@ const props = withDefaults(
|
|
|
49
50
|
);
|
|
50
51
|
|
|
51
52
|
const desc = computed(() => props.marker?.historyDescription || '未修改的初始状态');
|
|
53
|
+
/** 基线(初始状态)是否为最近一次保存点:仅页面栈的 `initial` 基线 step 会被 markSaved 标记。 */
|
|
54
|
+
const saved = computed(() => Boolean(props.marker?.saved));
|
|
52
55
|
const time = computed(() => formatHistoryTime(props.marker?.timestamp));
|
|
53
56
|
const timeTitle = computed(() => formatHistoryFullTime(props.marker?.timestamp));
|
|
54
57
|
const rowTitle = computed(() => {
|
|
@@ -5,10 +5,6 @@ import { datetimeFormatter } from '@tmagic/form';
|
|
|
5
5
|
import { useServices } from '@editor/hooks/use-services';
|
|
6
6
|
import type {
|
|
7
7
|
BaseStepValue,
|
|
8
|
-
CodeBlockHistoryGroup,
|
|
9
|
-
CodeBlockStepValue,
|
|
10
|
-
DataSourceHistoryGroup,
|
|
11
|
-
DataSourceStepValue,
|
|
12
8
|
HistoryOpSource,
|
|
13
9
|
HistoryOpType,
|
|
14
10
|
HistoryRowDescriptor,
|
|
@@ -227,12 +223,14 @@ export const toRowGroup = <T extends BaseStepValue = BaseStepValue>(
|
|
|
227
223
|
): HistoryRowGroup => {
|
|
228
224
|
const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
|
|
229
225
|
const timestamp = groupTimestamp(group);
|
|
226
|
+
// 无 describeGroup 时回退到组内最后一步的 describeStep:数据源/代码块不做相邻合并,每组恒为单步,二者等价。
|
|
227
|
+
const lastStep = group.steps[group.steps.length - 1]?.step;
|
|
230
228
|
return {
|
|
231
229
|
key,
|
|
232
230
|
applied: group.applied,
|
|
233
231
|
isCurrent: Boolean(group.isCurrent),
|
|
234
232
|
opType: group.opType,
|
|
235
|
-
desc: describeGroup(group),
|
|
233
|
+
desc: describeGroup ? describeGroup(group) : describeStep(lastStep),
|
|
236
234
|
source: groupSource(group),
|
|
237
235
|
time: formatHistoryTime(timestamp),
|
|
238
236
|
timeTitle: formatHistoryFullTime(timestamp),
|
|
@@ -273,30 +271,43 @@ const pickLastDescription = (descs: (string | undefined)[]): string | undefined
|
|
|
273
271
|
return undefined;
|
|
274
272
|
};
|
|
275
273
|
|
|
276
|
-
|
|
274
|
+
/**
|
|
275
|
+
* 页面 / 数据源 / 代码块三类历史共用的单步描述核心。
|
|
276
|
+
* 各类型只在「取展示名」与「实体单位名」上有差异,通过参数注入,文案模板完全一致:
|
|
277
|
+
* - 新增 / 删除:单实体展示「label」,多实体(仅页面可能出现)退化为「N 个X」;
|
|
278
|
+
* - 修改:展示「label · propPath」,无 diff 时兜底「X」,多实体退化为「N 个X」。
|
|
279
|
+
* 操作类型(新增 / 删除 / 修改)已由列表行的 op 徽标单独展示,故描述文案不再重复动词。
|
|
280
|
+
* 展示 id 统一取 schema.id;调用方显式传入的 historyDescription 永远优先。
|
|
281
|
+
*/
|
|
282
|
+
export const describeStep = <T>(
|
|
283
|
+
step: BaseStepValue<T>,
|
|
284
|
+
getLabel: (_schema?: T) => string | number | undefined,
|
|
285
|
+
unit: string,
|
|
286
|
+
): string => {
|
|
277
287
|
if (step.historyDescription) return step.historyDescription;
|
|
278
|
-
const { opType } = step;
|
|
279
288
|
const items = step.diff ?? [];
|
|
280
|
-
|
|
281
|
-
|
|
289
|
+
const label = (schema?: T) => labelWithId(getLabel(schema), (schema as { id?: string | number } | undefined)?.id);
|
|
290
|
+
|
|
291
|
+
if (step.opType === 'add') {
|
|
282
292
|
const node = items[0]?.newSchema;
|
|
283
|
-
return
|
|
293
|
+
return items.length === 1 && node ? label(node) : `${items.length} 个${unit}`;
|
|
284
294
|
}
|
|
285
|
-
if (opType === 'remove') {
|
|
286
|
-
const count = items.length;
|
|
295
|
+
if (step.opType === 'remove') {
|
|
287
296
|
const node = items[0]?.oldSchema;
|
|
288
|
-
return
|
|
297
|
+
return items.length === 1 && node ? label(node) : `${items.length} 个${unit}`;
|
|
289
298
|
}
|
|
290
|
-
if (!items.length) return
|
|
299
|
+
if (!items.length) return unit;
|
|
291
300
|
if (items.length === 1) {
|
|
292
|
-
const { newSchema, changeRecords } = items[0];
|
|
293
|
-
const propPath = changeRecords?.
|
|
294
|
-
const target =
|
|
295
|
-
return
|
|
301
|
+
const { newSchema, oldSchema, changeRecords } = items[0];
|
|
302
|
+
const propPath = changeRecords?.map((changeRecord) => changeRecord.propPath).join(',');
|
|
303
|
+
const target = label(newSchema ?? oldSchema);
|
|
304
|
+
return propPath ? `${target} · ${propPath}` : target;
|
|
296
305
|
}
|
|
297
|
-
return
|
|
306
|
+
return `${items.length} 个${unit}`;
|
|
298
307
|
};
|
|
299
308
|
|
|
309
|
+
export const describePageStep = (step: StepValue): string => describeStep(step, (node) => nameOf(node), '节点');
|
|
310
|
+
|
|
300
311
|
/**
|
|
301
312
|
* 合并组的展示文案:
|
|
302
313
|
* - 若组内任一步显式提供了 historyDescription:取最后一条非空 historyDescription(最近一次的描述更准确);
|
|
@@ -307,68 +318,8 @@ export const describePageGroup = (group: PageHistoryGroup) => {
|
|
|
307
318
|
const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
|
|
308
319
|
if (lastDesc) return lastDesc;
|
|
309
320
|
if (group.steps.length === 1) return describePageStep(group.steps[0].step);
|
|
310
|
-
const paths = new Set<string>();
|
|
311
|
-
group.steps.forEach((s) => {
|
|
312
|
-
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
313
|
-
});
|
|
314
|
-
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
315
|
-
const target = labelWithId(
|
|
316
|
-
group.targetName ?? (group.targetId !== undefined ? `${group.targetId}` : '节点'),
|
|
317
|
-
group.targetId,
|
|
318
|
-
);
|
|
319
|
-
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
320
|
-
};
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
if (step.historyDescription) return step.historyDescription;
|
|
324
|
-
const { oldSchema: oldSchema, newSchema: newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
325
|
-
if (!oldSchema && newSchema) return `创建 ${labelWithId(newSchema.title, newSchema.id ?? step.id)}`;
|
|
326
|
-
if (!newSchema && oldSchema) return `删除 ${labelWithId(oldSchema.title, oldSchema.id ?? step.id)}`;
|
|
327
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
328
|
-
const title = labelWithId(newSchema?.title || oldSchema?.title, step.id);
|
|
329
|
-
return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export const describeDataSourceGroup = (group: DataSourceHistoryGroup) => {
|
|
333
|
-
const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
|
|
334
|
-
if (lastDesc) return lastDesc;
|
|
335
|
-
if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
|
|
336
|
-
const paths = new Set<string>();
|
|
337
|
-
group.steps.forEach((s) => {
|
|
338
|
-
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
339
|
-
});
|
|
340
|
-
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
341
|
-
const rawTitle =
|
|
342
|
-
group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.title ||
|
|
343
|
-
group.steps[0].step.diff?.[0]?.oldSchema?.title;
|
|
344
|
-
const target = labelWithId(rawTitle, group.id);
|
|
345
|
-
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
export const describeCodeBlockStep = (step: CodeBlockStepValue) => {
|
|
349
|
-
if (step.historyDescription) return step.historyDescription;
|
|
350
|
-
const { oldSchema: oldContent, newSchema: newContent, changeRecords } = step.diff?.[0] ?? {};
|
|
351
|
-
if (!oldContent && newContent) return `创建 ${labelWithId(newContent.name, newContent.id ?? step.id)}`;
|
|
352
|
-
if (!newContent && oldContent) return `删除 ${labelWithId(oldContent.name, oldContent.id ?? step.id)}`;
|
|
353
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
354
|
-
const title = labelWithId(newContent?.name || oldContent?.name, step.id);
|
|
355
|
-
return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
export const describeCodeBlockGroup = (group: CodeBlockHistoryGroup) => {
|
|
359
|
-
const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
|
|
360
|
-
if (lastDesc) return lastDesc;
|
|
361
|
-
if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
|
|
362
|
-
const paths = new Set<string>();
|
|
363
|
-
group.steps.forEach((s) => {
|
|
364
|
-
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
365
|
-
});
|
|
366
|
-
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
367
|
-
const rawName =
|
|
368
|
-
group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.name ||
|
|
369
|
-
group.steps[0].step.diff?.[0]?.oldSchema?.name;
|
|
370
|
-
const target = labelWithId(rawName, group.id);
|
|
371
|
-
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
322
|
+
return labelWithId(group.targetName ?? (group.targetId !== undefined ? `${group.targetId}` : '节点'), group.targetId);
|
|
372
323
|
};
|
|
373
324
|
|
|
374
325
|
/**
|
|
@@ -385,22 +336,11 @@ export const isPageStepRevertable = (step: StepValue): boolean => {
|
|
|
385
336
|
};
|
|
386
337
|
|
|
387
338
|
/**
|
|
388
|
-
*
|
|
389
|
-
* - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
|
|
390
|
-
* - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
391
|
-
*/
|
|
392
|
-
export const isDataSourceStepRevertable = (step: DataSourceStepValue): boolean => {
|
|
393
|
-
const item = step.diff?.[0];
|
|
394
|
-
if (!item?.oldSchema || !item?.newSchema) return true;
|
|
395
|
-
return Boolean(item.changeRecords?.length);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* 代码块 step 是否支持「回滚」:
|
|
339
|
+
* 单 diff 项历史(数据源 / 代码块)是否支持「回滚」:
|
|
400
340
|
* - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
|
|
401
|
-
* -
|
|
341
|
+
* - 更新(前后内容都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
402
342
|
*/
|
|
403
|
-
export const
|
|
343
|
+
export const isSingleDiffStepRevertable = (step: BaseStepValue): boolean => {
|
|
404
344
|
const item = step.diff?.[0];
|
|
405
345
|
if (!item?.oldSchema || !item?.newSchema) return true;
|
|
406
346
|
return Boolean(item.changeRecords?.length);
|
|
@@ -257,7 +257,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|
|
257
257
|
slots: {},
|
|
258
258
|
},
|
|
259
259
|
layer: {
|
|
260
|
-
$key:
|
|
260
|
+
$key: SideItemKey.LAYER,
|
|
261
261
|
type: 'component',
|
|
262
262
|
icon: List,
|
|
263
263
|
text: '已选组件',
|
|
@@ -277,7 +277,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|
|
277
277
|
slots: {},
|
|
278
278
|
},
|
|
279
279
|
[SideItemKey.CODE_BLOCK]: {
|
|
280
|
-
$key:
|
|
280
|
+
$key: SideItemKey.CODE_BLOCK,
|
|
281
281
|
type: 'component',
|
|
282
282
|
icon: EditPen,
|
|
283
283
|
text: '代码编辑',
|