@tmagic/editor 1.8.0-beta.11 → 1.8.0-beta.13
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 +7 -4
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +18 -123
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +60 -14
- package/dist/es/components/ScrollBar.js +0 -1
- package/dist/es/components/ViewForm.js +5 -0
- package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/es/editorProps.js +6 -1
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +15 -3
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +2 -17
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +7 -0
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +5 -17
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +29 -3
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +50 -88
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +2 -5
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +48 -23
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/UISelect.js +0 -1
- package/dist/es/hooks/use-compare-form.js +119 -0
- package/dist/es/index.js +13 -8
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +21 -6
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/layouts/history-list/composables.js +1 -1
- package/dist/es/layouts/history-list/useHistoryRevert.js +7 -3
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +44 -5
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +15 -8
- package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +15 -31
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/code-block/useContentMenu.js +18 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +18 -3
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/plugin.js +3 -1
- package/dist/es/services/codeBlock.js +1 -1
- package/dist/es/services/editor.js +104 -5
- package/dist/es/services/history.js +1 -1
- package/dist/es/services/props.js +3 -2
- package/dist/es/services/storage.js +2 -2
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +104 -55
- package/dist/es/utils/data-source/index.js +30 -11
- package/dist/es/utils/event.js +123 -0
- package/dist/es/utils/monaco-editor.js +22 -6
- package/dist/es/utils/props.js +119 -8
- package/dist/es/utils/type-match-rules.js +360 -0
- package/dist/style.css +104 -55
- package/dist/tmagic-editor.umd.cjs +16996 -15806
- package/package.json +8 -8
- package/src/Editor.vue +3 -1
- package/src/components/CodeBlockEditor.vue +3 -0
- package/src/components/CompareForm.vue +16 -196
- package/src/components/FloatingBox.vue +77 -8
- package/src/components/ScrollBar.vue +0 -67
- package/src/components/ViewForm.vue +55 -0
- package/src/editorProps.ts +13 -0
- package/src/fields/CodeSelect.vue +4 -1
- package/src/fields/CondOpSelect.vue +2 -24
- package/src/fields/DataSourceFields.vue +5 -0
- package/src/fields/DataSourceInput.vue +7 -25
- package/src/fields/DataSourceMocks.vue +3 -0
- package/src/fields/DisplayConds.vue +14 -0
- package/src/fields/EventSelect.vue +84 -129
- package/src/fields/StyleSetter/Index.vue +2 -8
- package/src/fields/StyleSetter/pro/Background.vue +94 -55
- package/src/fields/StyleSetter/pro/Border.vue +9 -11
- package/src/fields/StyleSetter/pro/Font.vue +66 -64
- package/src/fields/StyleSetter/pro/Layout.vue +140 -138
- package/src/fields/StyleSetter/pro/Position.vue +69 -67
- package/src/fields/StyleSetter/pro/Transform.vue +23 -25
- package/src/fields/UISelect.vue +0 -12
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-compare-form.ts +187 -0
- package/src/index.ts +2 -0
- package/src/layouts/CodeEditor.vue +32 -6
- package/src/layouts/Framework.vue +7 -6
- package/src/layouts/history-list/HistoryDiffDialog.vue +11 -1
- package/src/layouts/history-list/useHistoryRevert.ts +4 -0
- package/src/layouts/props-panel/FormPanel.vue +66 -5
- package/src/layouts/props-panel/PropsPanel.vue +20 -6
- package/src/layouts/props-panel/use-style-panel.ts +4 -3
- package/src/layouts/sidebar/Sidebar.vue +2 -0
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +4 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +2 -2
- package/src/layouts/sidebar/data-source/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
- package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
- package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
- package/src/plugin.ts +3 -1
- package/src/services/editor.ts +168 -3
- package/src/services/ui.ts +6 -0
- package/src/theme/event.scss +8 -0
- package/src/theme/floating-box.scss +9 -1
- package/src/theme/layer-node-content.scss +14 -0
- package/src/theme/scroll-bar.scss +64 -0
- package/src/theme/theme.scss +3 -0
- package/src/theme/ui-select.scss +9 -0
- package/src/type.ts +105 -2
- package/src/utils/data-source/index.ts +30 -12
- package/src/utils/event.ts +224 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/monaco-editor.ts +27 -0
- package/src/utils/props.ts +189 -2
- package/src/utils/type-match-rules.ts +678 -0
- package/types/index.d.ts +558 -213
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<MContainer
|
|
3
|
+
:prop="prop"
|
|
3
4
|
:config="config"
|
|
4
5
|
:model="values"
|
|
5
6
|
:last-values="lastValues"
|
|
@@ -21,6 +22,7 @@ defineProps<{
|
|
|
21
22
|
isCompare?: boolean;
|
|
22
23
|
disabled?: boolean;
|
|
23
24
|
size?: 'large' | 'default' | 'small';
|
|
25
|
+
prop?: string;
|
|
24
26
|
}>();
|
|
25
27
|
|
|
26
28
|
const emit = defineEmits<{
|
|
@@ -29,33 +31,29 @@ const emit = defineEmits<{
|
|
|
29
31
|
}>();
|
|
30
32
|
|
|
31
33
|
const config = defineFormItem({
|
|
34
|
+
name: 'transform',
|
|
32
35
|
items: [
|
|
33
36
|
{
|
|
34
|
-
name: '
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fieldConfig: {
|
|
55
|
-
type: 'text',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
37
|
+
name: 'rotate',
|
|
38
|
+
text: '旋转角度',
|
|
39
|
+
labelWidth: '68px',
|
|
40
|
+
type: 'data-source-field-select',
|
|
41
|
+
checkStrictly: false,
|
|
42
|
+
dataSourceFieldType: ['string', 'number'],
|
|
43
|
+
fieldConfig: {
|
|
44
|
+
type: 'text',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'scale',
|
|
49
|
+
text: '缩放',
|
|
50
|
+
labelWidth: '68px',
|
|
51
|
+
type: 'data-source-field-select',
|
|
52
|
+
checkStrictly: false,
|
|
53
|
+
dataSourceFieldType: ['string', 'number'],
|
|
54
|
+
fieldConfig: {
|
|
55
|
+
type: 'text',
|
|
56
|
+
},
|
|
59
57
|
},
|
|
60
58
|
],
|
|
61
59
|
});
|
package/src/fields/UISelect.vue
CHANGED
|
@@ -127,15 +127,3 @@ const unhighlight = () => {
|
|
|
127
127
|
stageOverlayService.get('stage')?.clearHighlight();
|
|
128
128
|
};
|
|
129
129
|
</script>
|
|
130
|
-
|
|
131
|
-
<style lang="scss">
|
|
132
|
-
.m-fields-ui-select {
|
|
133
|
-
cursor: pointer;
|
|
134
|
-
i {
|
|
135
|
-
margin-right: 3px;
|
|
136
|
-
}
|
|
137
|
-
span {
|
|
138
|
-
color: #2882e0;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
</style>
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Tencent. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { computed, type ComputedRef, inject, provide, type Ref, ref, useTemplateRef, watch, watchEffect } from 'vue';
|
|
20
|
+
|
|
21
|
+
import type { CodeBlockContent, MNode } from '@tmagic/core';
|
|
22
|
+
import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form';
|
|
23
|
+
|
|
24
|
+
import type { CompareFormBaseProps } from '@editor/type';
|
|
25
|
+
import { getCodeBlockFormConfig } from '@editor/utils/code-block';
|
|
26
|
+
import { removeStyleDisplayConfig } from '@editor/utils/props';
|
|
27
|
+
|
|
28
|
+
export interface UseCompareFormReturn {
|
|
29
|
+
config: Ref<FormConfig>;
|
|
30
|
+
currentValues: ComputedRef<FormValue>;
|
|
31
|
+
wrapperStyle: ComputedRef<Record<string, string> | undefined>;
|
|
32
|
+
mergedExtendState: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
33
|
+
loadConfig: () => Promise<void>;
|
|
34
|
+
formRef: Readonly<Ref<InstanceType<typeof MForm> | null>>;
|
|
35
|
+
normalizeCodeBlockValue: (v: Partial<CodeBlockContent> | Record<string, any> | undefined) => Record<string, any>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* CompareForm / ViewForm 共用逻辑:
|
|
40
|
+
* - 按 `category`(node / data-source / code-block) 加载 FormConfig(支持自定义 `loadConfig`);
|
|
41
|
+
* - 代码块 `content` 归一化为字符串;
|
|
42
|
+
* - 外层容器固定高度 + 内部滚动的 `wrapperStyle`;
|
|
43
|
+
* - 将 services / stage 注入 MForm.formState,保证 filterFunction 上下文一致。
|
|
44
|
+
*
|
|
45
|
+
* 两个组件的差异仅在于是否做新旧值对比,这部分逻辑保留在各自组件中。
|
|
46
|
+
*/
|
|
47
|
+
export const useCompareForm = (props: CompareFormBaseProps): UseCompareFormReturn => {
|
|
48
|
+
provide('services', props.services);
|
|
49
|
+
|
|
50
|
+
const config = ref<FormConfig>([]);
|
|
51
|
+
|
|
52
|
+
/** vs-code 编辑器的 monaco 配置项,沿用 Editor 顶层 provide('codeOptions', ...) 的注入。 */
|
|
53
|
+
const codeOptions = inject<Record<string, any>>('codeOptions', {});
|
|
54
|
+
|
|
55
|
+
/** 将代码块的 content 字段统一成字符串,便于在表单 / 对比中展示 */
|
|
56
|
+
const normalizeCodeBlockValue = (
|
|
57
|
+
v: Partial<CodeBlockContent> | Record<string, any> | undefined,
|
|
58
|
+
): Record<string, any> => {
|
|
59
|
+
if (!v) return {};
|
|
60
|
+
const next: Record<string, any> = { ...v };
|
|
61
|
+
if (next.content && typeof next.content !== 'string') {
|
|
62
|
+
try {
|
|
63
|
+
next.content = next.content.toString();
|
|
64
|
+
} catch {
|
|
65
|
+
next.content = '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return next;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const currentValues = computed<FormValue>(() => {
|
|
72
|
+
if (props.category === 'code-block') {
|
|
73
|
+
return normalizeCodeBlockValue(props.value as Partial<CodeBlockContent>);
|
|
74
|
+
}
|
|
75
|
+
return (props.value || {}) as FormValue;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 外层包裹层的样式:当传入 `height` 时启用固定高度 + 内部滚动,
|
|
80
|
+
* 这样滚动条会出现在组件内部,避免父容器(如 Dialog)自身也产生滚动。
|
|
81
|
+
*/
|
|
82
|
+
const wrapperStyle = computed(() => {
|
|
83
|
+
if (!props.height) return undefined;
|
|
84
|
+
const style: Record<string, string> = {
|
|
85
|
+
height: props.height,
|
|
86
|
+
overflow: 'auto',
|
|
87
|
+
};
|
|
88
|
+
return style;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const mergedExtendState = (state: FormState) => {
|
|
92
|
+
const extendState = props.extendState ?? ((s: FormState) => s);
|
|
93
|
+
return extendState(props.baseFormState || state);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
|
|
98
|
+
* 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
|
|
99
|
+
*/
|
|
100
|
+
const defaultLoadConfig = async (): Promise<FormConfig> => {
|
|
101
|
+
if (!props.services) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
switch (props.category) {
|
|
106
|
+
case 'node': {
|
|
107
|
+
if (!props.type) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return removeStyleDisplayConfig(
|
|
111
|
+
await props.services.propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
case 'data-source': {
|
|
115
|
+
const config = props.services.dataSourceService.getFormConfig(props.type || 'base');
|
|
116
|
+
// 数据源表单外层 tab 的「数据定义」项 status 为 'fields',tab-pane name 随之为 'fields'。
|
|
117
|
+
// 未显式设置 active 时,Tabs 默认取 '0',与 'fields' 不匹配会导致打开弹窗时无默认激活项,
|
|
118
|
+
// 这里与 DataSourceConfigPanel 保持一致,默认激活「数据定义」tab。
|
|
119
|
+
return config.map((item) => ('type' in item && item.type === 'tab' ? { ...item, active: 'fields' } : item));
|
|
120
|
+
}
|
|
121
|
+
case 'code-block': {
|
|
122
|
+
return getCodeBlockFormConfig({
|
|
123
|
+
paramColConfig: props.services.codeBlockService.getParamsColConfig(),
|
|
124
|
+
// 通过传入 dataSourceType 间接表达"是数据源代码块"——在对比 / 展示场景下 props.dataSourceType
|
|
125
|
+
// 由调用方按 step 上下文显式传入,未传则视为普通代码块,「执行时机」字段隐藏。
|
|
126
|
+
isDataSource: () => Boolean(props.dataSourceType),
|
|
127
|
+
dataSourceType: () => props.dataSourceType,
|
|
128
|
+
codeOptions,
|
|
129
|
+
// 对比 / 展示模式只读,不需要校验/语法检查
|
|
130
|
+
editable: false,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const loadConfig = async () => {
|
|
139
|
+
if (props.loadConfig) {
|
|
140
|
+
config.value = await props.loadConfig({
|
|
141
|
+
category: props.category as string,
|
|
142
|
+
type: props.type,
|
|
143
|
+
dataSourceType: props.dataSourceType,
|
|
144
|
+
defaultLoadConfig,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
config.value = await defaultLoadConfig();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
watch(
|
|
153
|
+
[() => props.category, () => props.type, () => props.dataSourceType, () => props.loadConfig],
|
|
154
|
+
() => {
|
|
155
|
+
loadConfig();
|
|
156
|
+
},
|
|
157
|
+
{ immediate: true },
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const formRef = useTemplateRef<InstanceType<typeof MForm>>('form');
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 把 services / stage 注入 MForm 的 formState,避免 propsService 注入的表单配置中
|
|
164
|
+
* 形如 `display: ({ services }) => services.uiService.get(...)` 的 filterFunction
|
|
165
|
+
* 在执行时拿不到 `formState.services` 而报错。
|
|
166
|
+
*
|
|
167
|
+
* 与 props-panel/FormPanel.vue 中的注入方式保持一致:
|
|
168
|
+
* - services:整个 useServices() 返回的服务集合;
|
|
169
|
+
* - stage:当前 editorService.get('stage') 的最新值。
|
|
170
|
+
*/
|
|
171
|
+
watchEffect(() => {
|
|
172
|
+
if (formRef.value && props.services) {
|
|
173
|
+
formRef.value.formState.stage = props.services.editorService.get('stage');
|
|
174
|
+
formRef.value.formState.services = props.services;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
config,
|
|
180
|
+
currentValues,
|
|
181
|
+
wrapperStyle,
|
|
182
|
+
mergedExtendState,
|
|
183
|
+
loadConfig,
|
|
184
|
+
formRef,
|
|
185
|
+
normalizeCodeBlockValue,
|
|
186
|
+
};
|
|
187
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { default as componentListService } from './services/componentList';
|
|
|
48
48
|
export { default as keybindingService } from './services/keybinding';
|
|
49
49
|
export { default as ComponentListPanel } from './layouts/sidebar/ComponentListPanel.vue';
|
|
50
50
|
export { default as LayerPanel } from './layouts/sidebar/layer/LayerPanel.vue';
|
|
51
|
+
export { default as LayerNodeContent } from './layouts/sidebar/layer/LayerNodeContent.vue';
|
|
51
52
|
export { default as CodeSelect } from './fields/CodeSelect.vue';
|
|
52
53
|
export { default as CodeSelectCol } from './fields/CodeSelectCol.vue';
|
|
53
54
|
export { default as DataSourceFields } from './fields/DataSourceFields.vue';
|
|
@@ -73,6 +74,7 @@ export { default as SplitView } from './components/SplitView.vue';
|
|
|
73
74
|
export { default as Resizer } from './components/Resizer.vue';
|
|
74
75
|
export { default as CodeBlockEditor } from './components/CodeBlockEditor.vue';
|
|
75
76
|
export { default as CompareForm } from './components/CompareForm.vue';
|
|
77
|
+
export { default as ViewForm } from './components/ViewForm.vue';
|
|
76
78
|
export { default as HistoryListBucket } from './layouts/history-list/Bucket.vue';
|
|
77
79
|
export { default as HistoryListBucketTab } from './layouts/history-list/BucketTab.vue';
|
|
78
80
|
export { default as HistoryDiffDialog } from './layouts/history-list/HistoryDiffDialog.vue';
|
|
@@ -194,6 +194,9 @@ const values = ref('');
|
|
|
194
194
|
const loading = ref(false);
|
|
195
195
|
const codeEditorEl = useTemplateRef<HTMLDivElement>('codeEditor');
|
|
196
196
|
|
|
197
|
+
// 组件是否已卸载,避免异步初始化过程中组件被销毁后继续创建编辑器
|
|
198
|
+
let destroyed = false;
|
|
199
|
+
|
|
197
200
|
const resizeObserver = new globalThis.ResizeObserver(
|
|
198
201
|
throttle((): void => {
|
|
199
202
|
vsEditor?.layout();
|
|
@@ -254,11 +257,18 @@ const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
254
257
|
}
|
|
255
258
|
};
|
|
256
259
|
|
|
260
|
+
// 判断容器是否仍然有效:组件未卸载、ref 存在且已挂载到文档树中
|
|
261
|
+
// Monaco 在创建编辑器时会向上遍历 parentNode 判断是否处于 shadow DOM,
|
|
262
|
+
// 若容器为 null 或已从文档树脱离,将抛出 "Cannot read properties of null (reading 'parentNode')"
|
|
263
|
+
const isEditorElValid = () => !destroyed && !!codeEditorEl.value && codeEditorEl.value.isConnected;
|
|
264
|
+
|
|
257
265
|
const init = async () => {
|
|
258
|
-
if (!
|
|
266
|
+
if (!isEditorElValid()) return;
|
|
259
267
|
|
|
260
|
-
if (codeEditorEl.value
|
|
268
|
+
if (codeEditorEl.value!.clientHeight === 0) {
|
|
261
269
|
await nextTick();
|
|
270
|
+
// await 期间组件可能已被卸载或容器被移除
|
|
271
|
+
if (!isEditorElValid()) return;
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
// 重置缓存的额外高度,因为编辑器重新初始化
|
|
@@ -266,6 +276,11 @@ const init = async () => {
|
|
|
266
276
|
|
|
267
277
|
monaco = await loadMonaco();
|
|
268
278
|
|
|
279
|
+
// 加载 monaco 是异步过程,期间组件可能已被卸载或容器被移除
|
|
280
|
+
if (!isEditorElValid()) return;
|
|
281
|
+
|
|
282
|
+
const editorEl = codeEditorEl.value!;
|
|
283
|
+
|
|
269
284
|
const options = {
|
|
270
285
|
value: values.value,
|
|
271
286
|
language: props.language,
|
|
@@ -275,7 +290,7 @@ const init = async () => {
|
|
|
275
290
|
};
|
|
276
291
|
|
|
277
292
|
if (props.type === 'diff') {
|
|
278
|
-
vsDiffEditor = await getEditorConfig('customCreateMonacoDiffEditor')(monaco!,
|
|
293
|
+
vsDiffEditor = await getEditorConfig('customCreateMonacoDiffEditor')(monaco!, editorEl, options);
|
|
279
294
|
|
|
280
295
|
// 监听diff编辑器内容变化
|
|
281
296
|
vsDiffEditor.getModifiedEditor().onDidChangeModelContent(() => {
|
|
@@ -285,7 +300,7 @@ const init = async () => {
|
|
|
285
300
|
}
|
|
286
301
|
});
|
|
287
302
|
} else {
|
|
288
|
-
vsEditor = await getEditorConfig('customCreateMonacoEditor')(monaco!,
|
|
303
|
+
vsEditor = await getEditorConfig('customCreateMonacoEditor')(monaco!, editorEl, options);
|
|
289
304
|
|
|
290
305
|
// 监听编辑器内容变化
|
|
291
306
|
vsEditor.onDidChangeModelContent(() => {
|
|
@@ -296,10 +311,19 @@ const init = async () => {
|
|
|
296
311
|
});
|
|
297
312
|
}
|
|
298
313
|
|
|
314
|
+
// 编辑器创建可能是异步的,创建完成后若组件已卸载则立即销毁,避免内存泄漏
|
|
315
|
+
if (destroyed) {
|
|
316
|
+
vsEditor?.dispose();
|
|
317
|
+
vsDiffEditor?.dispose();
|
|
318
|
+
vsEditor = null;
|
|
319
|
+
vsDiffEditor = null;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
299
323
|
setEditorValue(props.initValues, props.modifiedValues);
|
|
300
324
|
|
|
301
325
|
emit('initd', vsEditor);
|
|
302
|
-
|
|
326
|
+
editorEl.addEventListener('keydown', handleKeyDown);
|
|
303
327
|
|
|
304
328
|
if (props.type !== 'diff' && props.autoSave) {
|
|
305
329
|
vsEditor?.onDidBlurEditorWidget(() => {
|
|
@@ -311,7 +335,7 @@ const init = async () => {
|
|
|
311
335
|
});
|
|
312
336
|
}
|
|
313
337
|
|
|
314
|
-
resizeObserver.observe(
|
|
338
|
+
resizeObserver.observe(editorEl);
|
|
315
339
|
};
|
|
316
340
|
|
|
317
341
|
watch(
|
|
@@ -362,6 +386,8 @@ onMounted(async () => {
|
|
|
362
386
|
});
|
|
363
387
|
|
|
364
388
|
onBeforeUnmount(() => {
|
|
389
|
+
destroyed = true;
|
|
390
|
+
|
|
365
391
|
resizeObserver.disconnect();
|
|
366
392
|
|
|
367
393
|
vsEditor?.dispose();
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
right-class="m-editor-framework-right"
|
|
26
26
|
:left="hideSidebar ? undefined : columnWidth.left"
|
|
27
27
|
:right="columnWidth.right"
|
|
28
|
-
:min-left="hideSidebar ? 0 :
|
|
29
|
-
:min-right="
|
|
30
|
-
:min-center="
|
|
28
|
+
:min-left="hideSidebar ? 0 : minLeftColumnWidth"
|
|
29
|
+
:min-right="minRightColumnWidth"
|
|
30
|
+
:min-center="minCenterColumnWidth"
|
|
31
31
|
:width="frameworkRect.width"
|
|
32
32
|
@change="columnWidthChange"
|
|
33
33
|
>
|
|
@@ -78,9 +78,6 @@ import { getEditorConfig } from '@editor/utils/config';
|
|
|
78
78
|
import {
|
|
79
79
|
DEFAULT_LEFT_COLUMN_WIDTH,
|
|
80
80
|
LEFT_COLUMN_WIDTH_STORAGE_KEY,
|
|
81
|
-
MIN_CENTER_COLUMN_WIDTH,
|
|
82
|
-
MIN_LEFT_COLUMN_WIDTH,
|
|
83
|
-
MIN_RIGHT_COLUMN_WIDTH,
|
|
84
81
|
RIGHT_COLUMN_WIDTH_STORAGE_KEY,
|
|
85
82
|
} from '@editor/utils/const';
|
|
86
83
|
|
|
@@ -116,6 +113,10 @@ const showSrc = computed(() => uiService.get('showSrc'));
|
|
|
116
113
|
|
|
117
114
|
const columnWidth = computed(() => uiService.get('columnWidth'));
|
|
118
115
|
|
|
116
|
+
const minLeftColumnWidth = computed(() => uiService.get('minLeftColumnWidth'));
|
|
117
|
+
const minCenterColumnWidth = computed(() => uiService.get('minCenterColumnWidth'));
|
|
118
|
+
const minRightColumnWidth = computed(() => uiService.get('minRightColumnWidth'));
|
|
119
|
+
|
|
119
120
|
watch(pageLength, () => {
|
|
120
121
|
splitViewRef.value?.updateWidth();
|
|
121
122
|
});
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
:load-config="loadConfig"
|
|
49
49
|
:self-diff-field-types="selfDiffFieldTypes"
|
|
50
50
|
:services="props.services"
|
|
51
|
+
:size="props.size"
|
|
51
52
|
height="70vh"
|
|
52
53
|
/>
|
|
53
54
|
|
|
@@ -77,7 +78,14 @@
|
|
|
77
78
|
import { computed, ref, watch } from 'vue';
|
|
78
79
|
import { isEqual } from 'lodash-es';
|
|
79
80
|
|
|
80
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
type FieldSize,
|
|
83
|
+
TMagicButton,
|
|
84
|
+
TMagicDialog,
|
|
85
|
+
TMagicRadioButton,
|
|
86
|
+
TMagicRadioGroup,
|
|
87
|
+
TMagicTag,
|
|
88
|
+
} from '@tmagic/design';
|
|
81
89
|
import type { FormState } from '@tmagic/form';
|
|
82
90
|
|
|
83
91
|
import CompareForm from '@editor/components/CompareForm.vue';
|
|
@@ -104,6 +112,8 @@ const props = withDefaults(
|
|
|
104
112
|
*/
|
|
105
113
|
loadConfig?: CompareFormLoadConfig;
|
|
106
114
|
width?: string;
|
|
115
|
+
/** 差异对比表单内组件的尺寸(透传给 CompareForm 的 `size`),可选 'large' | 'default' | 'small'。 */
|
|
116
|
+
size?: FieldSize;
|
|
107
117
|
isConfirm?: boolean;
|
|
108
118
|
onConfirm?: () => void;
|
|
109
119
|
selfDiffFieldTypes?: string[];
|
|
@@ -95,6 +95,7 @@ const mountHistoryDiffDialog = async (
|
|
|
95
95
|
selfDiffFieldTypes: options.selfDiffFieldTypes,
|
|
96
96
|
compareFormState: options.compareFormState,
|
|
97
97
|
width: options.width,
|
|
98
|
+
size: options.size ?? options.services?.uiService?.get('propsPanelSize'),
|
|
98
99
|
onClose: options.onClose,
|
|
99
100
|
});
|
|
100
101
|
if (options.appContext) {
|
|
@@ -223,6 +224,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
|
|
|
223
224
|
services,
|
|
224
225
|
...extra,
|
|
225
226
|
width: extra?.width ?? dialogWidth,
|
|
227
|
+
size: extra?.size,
|
|
226
228
|
});
|
|
227
229
|
}
|
|
228
230
|
return confirmRevert();
|
|
@@ -382,6 +384,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
|
|
|
382
384
|
loadConfig: revertOptions.loadConfig,
|
|
383
385
|
selfDiffFieldTypes: revertOptions.selfDiffFieldTypes,
|
|
384
386
|
width: revertOptions.width,
|
|
387
|
+
size: revertOptions.size,
|
|
385
388
|
});
|
|
386
389
|
if (!confirmed) return null;
|
|
387
390
|
return await revertOptions.revert();
|
|
@@ -399,6 +402,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
|
|
|
399
402
|
services,
|
|
400
403
|
...extra,
|
|
401
404
|
width: extra?.width ?? dialogWidth,
|
|
405
|
+
size: extra?.size,
|
|
402
406
|
});
|
|
403
407
|
};
|
|
404
408
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
:size="propsPanelSize"
|
|
13
13
|
:init-values="values"
|
|
14
14
|
:config="config"
|
|
15
|
+
:type-match-valid="true"
|
|
15
16
|
:extend-state="extendState"
|
|
16
17
|
@change="submit"
|
|
17
18
|
@error="errorHandler"
|
|
@@ -46,13 +47,16 @@
|
|
|
46
47
|
import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, useTemplateRef, watchEffect } from 'vue';
|
|
47
48
|
import { Document as DocumentIcon } from '@element-plus/icons-vue';
|
|
48
49
|
|
|
49
|
-
import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
|
|
50
|
+
import { TMagicButton, tMagicMessage, TMagicScrollbar } from '@tmagic/design';
|
|
50
51
|
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
|
|
51
52
|
import { MForm } from '@tmagic/form';
|
|
53
|
+
import { filterXSS } from '@tmagic/utils';
|
|
52
54
|
|
|
53
55
|
import MIcon from '@editor/components/Icon.vue';
|
|
56
|
+
import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
|
|
54
57
|
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
|
|
55
58
|
import { useServices } from '@editor/hooks/use-services';
|
|
59
|
+
import { validatePropsForm } from '@editor/utils/props';
|
|
56
60
|
|
|
57
61
|
import CodeEditor from '../CodeEditor.vue';
|
|
58
62
|
|
|
@@ -75,13 +79,15 @@ const props = defineProps<{
|
|
|
75
79
|
}>();
|
|
76
80
|
|
|
77
81
|
const emit = defineEmits<{
|
|
78
|
-
submit: [values: any, eventData?: ContainerChangeEventData];
|
|
82
|
+
submit: [values: any, eventData?: ContainerChangeEventData, error?: any];
|
|
79
83
|
'submit-error': [e: any];
|
|
80
84
|
'form-error': [e: any];
|
|
81
85
|
mounted: [internalInstance: any];
|
|
82
86
|
unmounted: [];
|
|
83
87
|
}>();
|
|
84
88
|
|
|
89
|
+
const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, false);
|
|
90
|
+
|
|
85
91
|
const services = useServices();
|
|
86
92
|
const { editorService, uiService } = services;
|
|
87
93
|
|
|
@@ -113,9 +119,16 @@ onUnmounted(() => {
|
|
|
113
119
|
const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
|
|
114
120
|
try {
|
|
115
121
|
const values = await configFormRef.value?.submitForm();
|
|
122
|
+
// 校验成功:正常更新节点(第三个参数 error 为空,表示清除该来源的错误记录)
|
|
116
123
|
emit('submit', values, eventData);
|
|
117
124
|
} catch (e: any) {
|
|
118
|
-
|
|
125
|
+
if (enablePropsFormValidate) {
|
|
126
|
+
// 启用校验联动:校验失败时仍以当前表单值更新节点,并把错误信息一并抛给上层记录
|
|
127
|
+
emit('submit', v, eventData, e);
|
|
128
|
+
} else {
|
|
129
|
+
// 未启用:保持原行为,校验失败丢弃本次改动
|
|
130
|
+
emit('submit-error', e);
|
|
131
|
+
}
|
|
119
132
|
}
|
|
120
133
|
};
|
|
121
134
|
|
|
@@ -123,8 +136,56 @@ const errorHandler = (e: any) => {
|
|
|
123
136
|
emit('form-error', e);
|
|
124
137
|
};
|
|
125
138
|
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
/**
|
|
140
|
+
* 将校验错误文案安全地转为用于弹窗展示的 HTML。
|
|
141
|
+
*
|
|
142
|
+
* 文案形如 `字段 -> 主文案\n\n建议`,多条以结构性 `<br>` 拼接。为在保留换行排版的同时
|
|
143
|
+
* 避免 `${value}`/字段名中可能夹带的 HTML 造成 XSS:先按结构性 `<br>` 拆分,对每段做 HTML
|
|
144
|
+
* 转义后再把段内换行 `\n` 替换为 `<br>`,最后用 `<br>` 拼回。
|
|
145
|
+
*/
|
|
146
|
+
const formatValidateErrorHtml = (error: string): string =>
|
|
147
|
+
error
|
|
148
|
+
.split(/<br\s*\/?>/i)
|
|
149
|
+
.map((segment) => filterXSS(segment).replaceAll('\n', '<br>'))
|
|
150
|
+
.join('<br>');
|
|
151
|
+
|
|
152
|
+
const saveCode = async (values: any) => {
|
|
153
|
+
const newValues = props.codeValueKey ? { [props.codeValueKey]: values } : values;
|
|
154
|
+
|
|
155
|
+
if (!enablePropsFormValidate) {
|
|
156
|
+
// 未启用校验联动:保持原行为,直接提交源码保存的值
|
|
157
|
+
emit('submit', newValues);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 启用校验联动:源码编辑器保存的值未经过表单交互,这里另建一个独立的 MForm 实例对最新配置
|
|
162
|
+
// 做一次静默校验(不复用、也不污染页面上正在展示的表单),并将校验结果(错误信息)随提交
|
|
163
|
+
// 一并抛给上层记录,使源码保存的错误状态与表单编辑保持一致。
|
|
164
|
+
try {
|
|
165
|
+
const error = await validatePropsForm({
|
|
166
|
+
config: props.config,
|
|
167
|
+
values: newValues,
|
|
168
|
+
appContext: internalInstance?.appContext ?? null,
|
|
169
|
+
services,
|
|
170
|
+
stage: stage.value,
|
|
171
|
+
typeMatchValid: true,
|
|
172
|
+
extendState: props.extendState,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (error) {
|
|
176
|
+
tMagicMessage({
|
|
177
|
+
type: 'error',
|
|
178
|
+
message: formatValidateErrorHtml(error),
|
|
179
|
+
dangerouslyUseHTMLString: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
emit('submit', newValues, undefined, error ? new Error(error) : undefined);
|
|
184
|
+
} catch (e: any) {
|
|
185
|
+
console.log('validateForm error', e);
|
|
186
|
+
// 静默校验本身出现异常(如初始化超时)时,退回到不携带错误信息的提交,避免阻塞源码保存
|
|
187
|
+
emit('submit', newValues);
|
|
188
|
+
}
|
|
128
189
|
};
|
|
129
190
|
|
|
130
191
|
defineExpose({ configForm: configFormRef, submit });
|