@tmagic/editor 1.8.0-beta.0 → 1.8.0-beta.2
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 +11 -2
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
- package/dist/es/components/CompareForm.js +5 -0
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +167 -0
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +12 -3
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +16 -3
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +14 -2
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
- package/dist/es/hooks/use-code-block-edit.js +2 -2
- package/dist/es/hooks/use-stage.js +15 -7
- package/dist/es/index.js +3 -1
- package/dist/es/initService.js +6 -6
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
- package/dist/es/layouts/history-list/Bucket.js +5 -0
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/es/layouts/history-list/CodeBlockTab.js +5 -0
- package/dist/es/layouts/history-list/CodeBlockTab.vue_vue_type_script_setup_true_lang.js +62 -0
- package/dist/es/layouts/history-list/DataSourceTab.js +5 -0
- package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +62 -0
- package/dist/es/layouts/history-list/GroupRow.js +5 -0
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +180 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +195 -0
- package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +270 -0
- package/dist/es/layouts/history-list/InitialRow.js +5 -0
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/es/layouts/history-list/PageTab.js +5 -0
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +99 -0
- package/dist/es/layouts/history-list/composables.js +154 -0
- package/dist/es/layouts/sidebar/layer/use-node-status.js +38 -4
- package/dist/es/services/BaseService.js +31 -68
- package/dist/es/services/codeBlock.js +205 -8
- package/dist/es/services/componentList.js +2 -2
- package/dist/es/services/dataSource.js +217 -8
- package/dist/es/services/dep.js +21 -9
- package/dist/es/services/editor.js +350 -160
- package/dist/es/services/events.js +2 -2
- package/dist/es/services/history.js +407 -12
- package/dist/es/services/keybinding.js +2 -2
- package/dist/es/services/props.js +8 -8
- package/dist/es/services/stageOverlay.js +2 -2
- package/dist/es/services/storage.js +2 -2
- package/dist/es/services/ui.js +2 -2
- package/dist/es/style.css +347 -1
- package/dist/es/utils/code-block.js +122 -0
- package/dist/es/utils/content-menu.js +4 -5
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor.js +3 -3
- package/dist/es/utils/undo-redo.js +20 -0
- package/dist/style.css +347 -1
- package/dist/tmagic-editor.umd.cjs +3273 -766
- package/package.json +7 -7
- package/src/Editor.vue +7 -0
- package/src/components/CodeBlockEditor.vue +20 -109
- package/src/components/CodeParams.vue +6 -0
- package/src/components/CompareForm.vue +225 -0
- package/src/editorProps.ts +2 -0
- package/src/fields/Code.vue +19 -3
- package/src/fields/CodeSelect.vue +17 -1
- package/src/fields/CodeSelectCol.vue +30 -2
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
- package/src/fields/DataSourceFieldSelect/Index.vue +9 -2
- package/src/fields/DataSourceFields.vue +12 -3
- package/src/fields/DataSourceMethodSelect.vue +4 -1
- package/src/fields/DataSourceMethods.vue +14 -4
- package/src/fields/DataSourceMocks.vue +13 -3
- package/src/fields/DataSourceSelect.vue +4 -1
- package/src/fields/EventSelect.vue +57 -8
- package/src/fields/KeyValue.vue +17 -4
- package/src/fields/StyleSetter/Index.vue +6 -0
- package/src/fields/StyleSetter/components/Border.vue +15 -1
- package/src/fields/StyleSetter/components/Box.vue +2 -0
- package/src/fields/StyleSetter/pro/Background.vue +15 -1
- package/src/fields/StyleSetter/pro/Border.vue +24 -2
- package/src/fields/StyleSetter/pro/Font.vue +15 -1
- package/src/fields/StyleSetter/pro/Layout.vue +17 -1
- package/src/fields/StyleSetter/pro/Position.vue +15 -1
- package/src/fields/StyleSetter/pro/Transform.vue +15 -1
- package/src/fields/UISelect.vue +5 -2
- package/src/hooks/use-code-block-edit.ts +5 -2
- package/src/hooks/use-stage.ts +23 -7
- package/src/index.ts +1 -0
- package/src/layouts/CodeEditor.vue +15 -0
- package/src/layouts/Framework.vue +10 -5
- package/src/layouts/NavMenu.vue +9 -0
- package/src/layouts/history-list/Bucket.vue +99 -0
- package/src/layouts/history-list/CodeBlockTab.vue +61 -0
- package/src/layouts/history-list/DataSourceTab.vue +61 -0
- package/src/layouts/history-list/GroupRow.vue +207 -0
- package/src/layouts/history-list/HistoryDiffDialog.vue +212 -0
- package/src/layouts/history-list/HistoryListPanel.vue +271 -0
- package/src/layouts/history-list/InitialRow.vue +41 -0
- package/src/layouts/history-list/PageTab.vue +94 -0
- package/src/layouts/history-list/composables.ts +195 -0
- package/src/layouts/sidebar/layer/use-node-status.ts +64 -12
- package/src/services/BaseService.ts +35 -86
- package/src/services/codeBlock.ts +256 -5
- package/src/services/dataSource.ts +257 -6
- package/src/services/dep.ts +32 -9
- package/src/services/editor.ts +454 -171
- package/src/services/history.ts +452 -8
- package/src/services/props.ts +2 -2
- package/src/theme/history-list-panel.scss +423 -0
- package/src/theme/theme.scss +1 -0
- package/src/type.ts +179 -6
- package/src/utils/code-block.ts +150 -0
- package/src/utils/content-menu.ts +8 -5
- package/src/utils/dep/worker.ts +5 -3
- package/src/utils/editor.ts +4 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/undo-redo.ts +23 -0
- package/types/index.d.ts +2107 -810
- package/dist/es/utils/compose.js +0 -44
- package/dist/es/utils/editor-history.js +0 -100
- package/src/utils/compose.ts +0 -52
- package/src/utils/editor-history.ts +0 -138
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<TMagicDialog
|
|
4
|
+
v-model="visible"
|
|
5
|
+
class="m-editor-history-diff-dialog"
|
|
6
|
+
title="查看修改差异"
|
|
7
|
+
width="900px"
|
|
8
|
+
top="5vh"
|
|
9
|
+
destroy-on-close
|
|
10
|
+
append-to-body
|
|
11
|
+
>
|
|
12
|
+
<div v-if="payload" class="m-editor-history-diff-dialog-body">
|
|
13
|
+
<div class="m-editor-history-diff-dialog-header">
|
|
14
|
+
<span class="m-editor-history-diff-dialog-target">{{ targetText }}</span>
|
|
15
|
+
<div class="m-editor-history-diff-dialog-controls">
|
|
16
|
+
<TMagicRadioGroup v-model="viewMode" size="small" class="m-editor-history-diff-dialog-view">
|
|
17
|
+
<TMagicRadioButton value="form">表单对比</TMagicRadioButton>
|
|
18
|
+
<TMagicRadioButton value="code">源码对比</TMagicRadioButton>
|
|
19
|
+
</TMagicRadioGroup>
|
|
20
|
+
|
|
21
|
+
<TMagicRadioGroup v-model="mode" size="small" class="m-editor-history-diff-dialog-mode">
|
|
22
|
+
<TMagicRadioButton value="before">与修改前对比</TMagicRadioButton>
|
|
23
|
+
<TMagicRadioButton value="current" :disabled="!hasCurrent">与当前对比</TMagicRadioButton>
|
|
24
|
+
</TMagicRadioGroup>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="m-editor-history-diff-dialog-legend">
|
|
29
|
+
<TMagicTag size="small" type="danger">{{ leftLabel }}</TMagicTag>
|
|
30
|
+
<span class="m-editor-history-diff-dialog-arrow">→</span>
|
|
31
|
+
<TMagicTag size="small" type="success">{{ rightLabel }}</TMagicTag>
|
|
32
|
+
<span v-if="mode === 'current' && isSameAsCurrent" class="m-editor-history-diff-dialog-tip">
|
|
33
|
+
当前值与该步修改后一致,无差异
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<CompareForm
|
|
38
|
+
v-if="viewMode === 'form'"
|
|
39
|
+
:category="payload.category"
|
|
40
|
+
:type="payload.type"
|
|
41
|
+
:data-source-type="payload.dataSourceType"
|
|
42
|
+
:value="rightValue"
|
|
43
|
+
:last-value="leftValue"
|
|
44
|
+
:extend-state="extendState"
|
|
45
|
+
height="70vh"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<CodeEditor
|
|
49
|
+
v-else
|
|
50
|
+
type="diff"
|
|
51
|
+
language="json"
|
|
52
|
+
:init-values="leftValue"
|
|
53
|
+
:modified-values="rightValue"
|
|
54
|
+
:options="codeDiffOptions"
|
|
55
|
+
disabled-full-screen
|
|
56
|
+
height="70vh"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<template #footer>
|
|
61
|
+
<TMagicButton size="small" @click="visible = false">关闭</TMagicButton>
|
|
62
|
+
</template>
|
|
63
|
+
</TMagicDialog>
|
|
64
|
+
</Teleport>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script lang="ts" setup>
|
|
68
|
+
import { computed, ref, watch } from 'vue';
|
|
69
|
+
import { isEqual } from 'lodash-es';
|
|
70
|
+
|
|
71
|
+
import { TMagicButton, TMagicDialog, TMagicRadioButton, TMagicRadioGroup, TMagicTag } from '@tmagic/design';
|
|
72
|
+
import type { FormState } from '@tmagic/form';
|
|
73
|
+
|
|
74
|
+
import CompareForm, { type CompareCategory } from '@editor/components/CompareForm.vue';
|
|
75
|
+
import CodeEditor from '@editor/layouts/CodeEditor.vue';
|
|
76
|
+
|
|
77
|
+
defineOptions({
|
|
78
|
+
name: 'MEditorHistoryDiffDialog',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
defineProps<{
|
|
82
|
+
/**
|
|
83
|
+
* 来自 Editor 顶层的 `extendFormState`,用于扩展 MForm.formState。
|
|
84
|
+
* 透传给 CompareForm,从而让差异对比时表单 item 中依赖业务上下文的
|
|
85
|
+
* `display` / `disabled` 等 filterFunction 正常工作。
|
|
86
|
+
*/
|
|
87
|
+
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
88
|
+
}>();
|
|
89
|
+
|
|
90
|
+
/** 差异对话框的入参 */
|
|
91
|
+
export interface DiffDialogPayload {
|
|
92
|
+
/** 表单类别 */
|
|
93
|
+
category: CompareCategory;
|
|
94
|
+
/** 节点类型 / 数据源类型 */
|
|
95
|
+
type?: string;
|
|
96
|
+
/** 代码块场景下的数据源类型 */
|
|
97
|
+
dataSourceType?: string;
|
|
98
|
+
/** 该 step 修改前的值(oldNode / oldSchema / oldContent) */
|
|
99
|
+
lastValue: Record<string, any>;
|
|
100
|
+
/** 该 step 修改后的值(newNode / newSchema / newContent) */
|
|
101
|
+
value: Record<string, any>;
|
|
102
|
+
/** 当前编辑器中实际的最新值;不传或为 null 时禁用「与当前对比」 */
|
|
103
|
+
currentValue?: Record<string, any> | null;
|
|
104
|
+
/** 用于标题展示的目标名称 */
|
|
105
|
+
targetLabel?: string;
|
|
106
|
+
/** 用于标题展示的目标 id */
|
|
107
|
+
id?: string | number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 差异对比模式:
|
|
112
|
+
* - before:该步骤修改前 vs 该步骤修改后(默认行为,体现这一步带来的变化)
|
|
113
|
+
* - current:该步骤修改后 vs 当前最新值(用于查看「该步骤之后是否还被改过」)
|
|
114
|
+
*/
|
|
115
|
+
type DiffMode = 'before' | 'current';
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 展示形态:
|
|
119
|
+
* - form:以属性表单形式逐字段对比(默认,可读性更好)
|
|
120
|
+
* - code:以 JSON 源码形式做整体 diff(贴近"看原始数据差异",可看到表单未覆盖的字段)
|
|
121
|
+
*/
|
|
122
|
+
type ViewMode = 'form' | 'code';
|
|
123
|
+
|
|
124
|
+
const visible = ref(false);
|
|
125
|
+
const payload = ref<DiffDialogPayload | null>(null);
|
|
126
|
+
const mode = ref<DiffMode>('before');
|
|
127
|
+
const viewMode = ref<ViewMode>('form');
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 源码对比始终只读,关闭小地图,强制左右并排(side-by-side)展示。
|
|
131
|
+
*
|
|
132
|
+
* monaco diff 编辑器在宽度低于 `renderSideBySideInlineBreakpoint`(默认 900px)时
|
|
133
|
+
* 会自动退化为 inline(上下/单栏)视图。本弹窗宽度约 900px,去掉内边距后编辑器实际
|
|
134
|
+
* 宽度小于该阈值,会被切到 inline。这里通过 `useInlineViewWhenSpaceIsLimited: false`
|
|
135
|
+
* 关闭该自动降级,确保始终保持左右两栏对比。
|
|
136
|
+
*/
|
|
137
|
+
const codeDiffOptions = {
|
|
138
|
+
readOnly: true,
|
|
139
|
+
tabSize: 2,
|
|
140
|
+
minimap: { enabled: false },
|
|
141
|
+
renderSideBySide: true,
|
|
142
|
+
useInlineViewWhenSpaceIsLimited: false,
|
|
143
|
+
scrollBeyondLastLine: false,
|
|
144
|
+
hideUnchangedRegions: {
|
|
145
|
+
enabled: true,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const hasCurrent = computed(() => payload.value?.currentValue !== undefined && payload.value?.currentValue !== null);
|
|
150
|
+
|
|
151
|
+
/** 左侧(旧/参照)值 */
|
|
152
|
+
const leftValue = computed<Record<string, any>>(() => {
|
|
153
|
+
if (!payload.value) return {};
|
|
154
|
+
if (mode.value === 'current') return payload.value.value;
|
|
155
|
+
return payload.value.lastValue;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** 右侧(新/对比)值 */
|
|
159
|
+
const rightValue = computed<Record<string, any>>(() => {
|
|
160
|
+
if (!payload.value) return {};
|
|
161
|
+
if (mode.value === 'current') return payload.value.currentValue || {};
|
|
162
|
+
return payload.value.value;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const leftLabel = computed(() => (mode.value === 'current' ? '该步修改后' : '修改前'));
|
|
166
|
+
const rightLabel = computed(() => (mode.value === 'current' ? '当前' : '修改后'));
|
|
167
|
+
|
|
168
|
+
/** 「与当前对比」模式下,若当前值与该步修改后值相等,则展示提示 */
|
|
169
|
+
const isSameAsCurrent = computed(() => {
|
|
170
|
+
if (mode.value !== 'current' || !payload.value) return false;
|
|
171
|
+
return isEqual(payload.value.value, payload.value.currentValue);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const targetText = computed(() => {
|
|
175
|
+
if (!payload.value) return '';
|
|
176
|
+
const categoryText: Record<CompareCategory, string> = {
|
|
177
|
+
node: '节点',
|
|
178
|
+
'data-source': '数据源',
|
|
179
|
+
'code-block': '代码块',
|
|
180
|
+
};
|
|
181
|
+
const prefix = categoryText[payload.value.category] || '';
|
|
182
|
+
const label = payload.value.targetLabel || payload.value.type || '';
|
|
183
|
+
const { id } = payload.value;
|
|
184
|
+
const labelWithId = id !== undefined && id !== '' ? `${label}(${id})` : label;
|
|
185
|
+
return [prefix, labelWithId].filter(Boolean).join(':');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const open = (p: DiffDialogPayload) => {
|
|
189
|
+
payload.value = p;
|
|
190
|
+
// 每次打开按需重置默认模式:有当前值时优先「与当前对比」更贴近"看现在差什么",否则回退到默认
|
|
191
|
+
mode.value = 'before';
|
|
192
|
+
// 默认回到表单对比形态,避免残留上一次选择的源码模式
|
|
193
|
+
viewMode.value = 'form';
|
|
194
|
+
visible.value = true;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const close = () => {
|
|
198
|
+
visible.value = false;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// 关闭后清理 payload,避免下一次打开时残留旧值闪现
|
|
202
|
+
watch(visible, (v) => {
|
|
203
|
+
if (!v) {
|
|
204
|
+
payload.value = null;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
defineExpose({
|
|
209
|
+
open,
|
|
210
|
+
close,
|
|
211
|
+
});
|
|
212
|
+
</script>
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TMagicPopover
|
|
3
|
+
popper-class="m-editor-history-list-popover"
|
|
4
|
+
placement="bottom"
|
|
5
|
+
trigger="click"
|
|
6
|
+
:visible="visible"
|
|
7
|
+
:width="660"
|
|
8
|
+
>
|
|
9
|
+
<div class="m-editor-history-list">
|
|
10
|
+
<TMagicTooltip effect="dark" placement="top" content="关闭">
|
|
11
|
+
<TMagicButton class="m-editor-history-list-close" size="small" link @click="visible = false">
|
|
12
|
+
<template #icon>
|
|
13
|
+
<MIcon :icon="CloseIcon"></MIcon>
|
|
14
|
+
</template>
|
|
15
|
+
</TMagicButton>
|
|
16
|
+
</TMagicTooltip>
|
|
17
|
+
|
|
18
|
+
<TMagicTabs v-model="activeTab" class="m-editor-history-list-tabs">
|
|
19
|
+
<component
|
|
20
|
+
:is="tabPaneComponent?.component || 'el-tab-pane'"
|
|
21
|
+
v-bind="tabPaneComponent?.props({ name: 'page', label: `页面 (${pageGroups.length})` }) || {}"
|
|
22
|
+
>
|
|
23
|
+
<PageTab
|
|
24
|
+
:list="pageGroupsDisplay"
|
|
25
|
+
:expanded="expanded"
|
|
26
|
+
@toggle="toggleGroup"
|
|
27
|
+
@goto="onPageGoto"
|
|
28
|
+
@goto-initial="onPageGotoInitial"
|
|
29
|
+
@diff-step="onPageDiff"
|
|
30
|
+
@revert-step="onPageRevert"
|
|
31
|
+
/>
|
|
32
|
+
</component>
|
|
33
|
+
|
|
34
|
+
<component
|
|
35
|
+
:is="tabPaneComponent?.component || 'el-tab-pane'"
|
|
36
|
+
v-bind="tabPaneComponent?.props({ name: 'data-source', label: `数据源 (${dataSourceGroups.length})` }) || {}"
|
|
37
|
+
>
|
|
38
|
+
<DataSourceTab
|
|
39
|
+
:buckets="dataSourceGroupsByTarget"
|
|
40
|
+
:expanded="expanded"
|
|
41
|
+
@toggle="toggleGroup"
|
|
42
|
+
@goto="onDataSourceGoto"
|
|
43
|
+
@goto-initial="onDataSourceGotoInitial"
|
|
44
|
+
@diff-step="onDataSourceDiff"
|
|
45
|
+
@revert-step="onDataSourceRevert"
|
|
46
|
+
/>
|
|
47
|
+
</component>
|
|
48
|
+
|
|
49
|
+
<component
|
|
50
|
+
:is="tabPaneComponent?.component || 'el-tab-pane'"
|
|
51
|
+
v-bind="tabPaneComponent?.props({ name: 'code-block', label: `代码块 (${codeBlockGroups.length})` }) || {}"
|
|
52
|
+
>
|
|
53
|
+
<CodeBlockTab
|
|
54
|
+
:buckets="codeBlockGroupsByTarget"
|
|
55
|
+
:expanded="expanded"
|
|
56
|
+
@toggle="toggleGroup"
|
|
57
|
+
@goto="onCodeBlockGoto"
|
|
58
|
+
@goto-initial="onCodeBlockGotoInitial"
|
|
59
|
+
@diff-step="onCodeBlockDiff"
|
|
60
|
+
@revert-step="onCodeBlockRevert"
|
|
61
|
+
/>
|
|
62
|
+
</component>
|
|
63
|
+
</TMagicTabs>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<template #reference>
|
|
67
|
+
<TMagicTooltip effect="dark" placement="bottom" content="历史记录">
|
|
68
|
+
<TMagicButton size="small" link @click="visible = !visible">
|
|
69
|
+
<template #icon>
|
|
70
|
+
<MIcon :icon="ClockIcon"></MIcon>
|
|
71
|
+
</template>
|
|
72
|
+
</TMagicButton>
|
|
73
|
+
</TMagicTooltip>
|
|
74
|
+
</template>
|
|
75
|
+
</TMagicPopover>
|
|
76
|
+
|
|
77
|
+
<HistoryDiffDialog ref="diffDialog" :extend-state="extendFormState" />
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<script lang="ts" setup>
|
|
81
|
+
/**
|
|
82
|
+
* 历史记录面板:在顶部 NavMenu 上点击图标打开 popover,分三个 tab:
|
|
83
|
+
* - 页面:当前活动页面的历史栈,连续修改同一节点的多步会被合并成一组
|
|
84
|
+
* - 数据源:以 dataSource.id 分组,每组内部相邻的连续 update 自动合并
|
|
85
|
+
* - 代码块:同上,按 codeBlock.id 分组并合并相邻 update
|
|
86
|
+
*
|
|
87
|
+
* 数据通过 historyService 暴露的聚合 API 读取,UI 仅用于只读展示,
|
|
88
|
+
* 同时支持点击任意一条记录跳转至该状态:
|
|
89
|
+
* - 页面 tab:调用 editorService.gotoPageStep(targetCursor)
|
|
90
|
+
* - 数据源 tab:调用 dataSourceService.goto(id, targetCursor)
|
|
91
|
+
* - 代码块 tab:调用 codeBlockService.goto(id, targetCursor)
|
|
92
|
+
*
|
|
93
|
+
* 这里的 targetCursor = 用户点击的 step.index + 1,即"应用至此步完成的状态"。
|
|
94
|
+
*
|
|
95
|
+
* 此外每条 step 上提供"查看差异"入口(仅在前后值都存在的 update 步骤显示),
|
|
96
|
+
* 点击后弹出 HistoryDiffDialog,使用 CompareForm 组件以表单形式展示新旧值差异。
|
|
97
|
+
*
|
|
98
|
+
* 各 tab 的内容拆分为独立的 SFC(PageTab / DataSourceTab / CodeBlockTab),
|
|
99
|
+
* 共享的描述生成与折叠状态在 composables.ts 中维护。
|
|
100
|
+
*/
|
|
101
|
+
import { inject, markRaw, ref, useTemplateRef } from 'vue';
|
|
102
|
+
import { Clock, Close } from '@element-plus/icons-vue';
|
|
103
|
+
|
|
104
|
+
import { getDesignConfig, TMagicButton, TMagicPopover, TMagicTabs, TMagicTooltip } from '@tmagic/design';
|
|
105
|
+
import type { FormState } from '@tmagic/form';
|
|
106
|
+
|
|
107
|
+
import MIcon from '@editor/components/Icon.vue';
|
|
108
|
+
import { useServices } from '@editor/hooks/use-services';
|
|
109
|
+
|
|
110
|
+
import CodeBlockTab from './CodeBlockTab.vue';
|
|
111
|
+
import { useHistoryList } from './composables';
|
|
112
|
+
import DataSourceTab from './DataSourceTab.vue';
|
|
113
|
+
import HistoryDiffDialog from './HistoryDiffDialog.vue';
|
|
114
|
+
import PageTab from './PageTab.vue';
|
|
115
|
+
|
|
116
|
+
defineOptions({
|
|
117
|
+
name: 'MEditorHistoryListPanel',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const ClockIcon = markRaw(Clock);
|
|
121
|
+
const CloseIcon = markRaw(Close);
|
|
122
|
+
const activeTab = ref<'page' | 'data-source' | 'code-block'>('page');
|
|
123
|
+
|
|
124
|
+
/** 面板显隐受控:reference 图标点击切换,右上角关闭按钮置为 false。 */
|
|
125
|
+
const visible = ref(false);
|
|
126
|
+
|
|
127
|
+
const tabPaneComponent = getDesignConfig('components')?.tabPane;
|
|
128
|
+
|
|
129
|
+
const { editorService, dataSourceService, codeBlockService, historyService } = useServices();
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 通过 inject 拿到 Editor 顶层注入的 `extendFormState`,转交给 HistoryDiffDialog
|
|
133
|
+
* 内部的 CompareForm,使差异对比表单的 filterFunction 能拿到完整的业务上下文。
|
|
134
|
+
* 未提供时为 undefined,CompareForm/MForm 会跳过 extendState 处理。
|
|
135
|
+
*/
|
|
136
|
+
const extendFormState = inject<((_state: FormState) => Record<string, any> | Promise<Record<string, any>>) | undefined>(
|
|
137
|
+
'extendFormState',
|
|
138
|
+
undefined,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const {
|
|
142
|
+
expanded,
|
|
143
|
+
toggleGroup,
|
|
144
|
+
pageGroups,
|
|
145
|
+
dataSourceGroups,
|
|
146
|
+
codeBlockGroups,
|
|
147
|
+
pageGroupsDisplay,
|
|
148
|
+
dataSourceGroupsByTarget,
|
|
149
|
+
codeBlockGroupsByTarget,
|
|
150
|
+
} = useHistoryList();
|
|
151
|
+
|
|
152
|
+
/** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
|
|
153
|
+
const indexToCursor = (index: number) => index + 1;
|
|
154
|
+
|
|
155
|
+
const onPageGoto = (index: number) => {
|
|
156
|
+
editorService.gotoPageStep(indexToCursor(index));
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const onDataSourceGoto = (id: string | number, index: number) => {
|
|
160
|
+
dataSourceService.goto(id, indexToCursor(index));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const onCodeBlockGoto = (id: string | number, index: number) => {
|
|
164
|
+
codeBlockService.goto(id, indexToCursor(index));
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* "回到初始状态" = 把对应栈 cursor 移到 0(全部已撤销)。
|
|
169
|
+
* 复用 service.goto*(0) 即可,所有真实 step 的反向应用由 service 层的 undo 链路完成。
|
|
170
|
+
*/
|
|
171
|
+
const onPageGotoInitial = () => {
|
|
172
|
+
editorService.gotoPageStep(0);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const onDataSourceGotoInitial = (id: string | number) => {
|
|
176
|
+
dataSourceService.goto(id, 0);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const onCodeBlockGotoInitial = (id: string | number) => {
|
|
180
|
+
codeBlockService.goto(id, 0);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
|
|
185
|
+
* 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
|
|
186
|
+
*/
|
|
187
|
+
const onPageRevert = (index: number) => {
|
|
188
|
+
editorService.revertPageStep(index);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const onDataSourceRevert = (id: string | number, index: number) => {
|
|
192
|
+
dataSourceService.revert(id, index);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const onCodeBlockRevert = (id: string | number, index: number) => {
|
|
196
|
+
codeBlockService.revert(id, index);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const diffDialogRef = useTemplateRef<InstanceType<typeof HistoryDiffDialog>>('diffDialog');
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 页面 step 差异:仅 update 单节点修改可对比,传入旧/新节点。
|
|
203
|
+
* 节点类型 `type` 优先取 newNode.type,再回退 oldNode.type。
|
|
204
|
+
* `currentValue` 取自 editorService 中该节点当前实际值,用于支持「与当前对比」。
|
|
205
|
+
*/
|
|
206
|
+
const onPageDiff = (index: number) => {
|
|
207
|
+
const groups = historyService.getPageHistoryGroups();
|
|
208
|
+
for (const group of groups) {
|
|
209
|
+
const entry = group.steps.find((s) => s.index === index);
|
|
210
|
+
if (!entry) continue;
|
|
211
|
+
const item = entry.step.updatedItems?.[0];
|
|
212
|
+
if (!item?.oldNode || !item?.newNode) return;
|
|
213
|
+
const type = (item.newNode.type as string) || (item.oldNode.type as string) || '';
|
|
214
|
+
const nodeId = item.newNode.id ?? item.oldNode.id;
|
|
215
|
+
const currentNode = nodeId !== undefined ? editorService.getNodeById(nodeId) : null;
|
|
216
|
+
diffDialogRef.value?.open({
|
|
217
|
+
category: 'node',
|
|
218
|
+
type,
|
|
219
|
+
lastValue: item.oldNode as Record<string, any>,
|
|
220
|
+
value: item.newNode as Record<string, any>,
|
|
221
|
+
currentValue: (currentNode as Record<string, any>) || null,
|
|
222
|
+
targetLabel: (item.newNode.name as string) || (item.oldNode.name as string) || type,
|
|
223
|
+
id: nodeId,
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const onDataSourceDiff = (id: string | number, index: number) => {
|
|
230
|
+
const groups = historyService.getDataSourceHistoryGroups();
|
|
231
|
+
for (const group of groups) {
|
|
232
|
+
if (group.id !== id) continue;
|
|
233
|
+
const entry = group.steps.find((s) => s.index === index);
|
|
234
|
+
if (!entry) continue;
|
|
235
|
+
const { oldSchema, newSchema } = entry.step;
|
|
236
|
+
if (!oldSchema || !newSchema) return;
|
|
237
|
+
const currentSchema = dataSourceService.getDataSourceById(`${id}`);
|
|
238
|
+
diffDialogRef.value?.open({
|
|
239
|
+
category: 'data-source',
|
|
240
|
+
type: newSchema.type || oldSchema.type || 'base',
|
|
241
|
+
lastValue: oldSchema as Record<string, any>,
|
|
242
|
+
value: newSchema as Record<string, any>,
|
|
243
|
+
currentValue: (currentSchema as Record<string, any>) || null,
|
|
244
|
+
targetLabel: newSchema.title || oldSchema.title || `${id}`,
|
|
245
|
+
id,
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const onCodeBlockDiff = (id: string | number, index: number) => {
|
|
252
|
+
const groups = historyService.getCodeBlockHistoryGroups();
|
|
253
|
+
for (const group of groups) {
|
|
254
|
+
if (group.id !== id) continue;
|
|
255
|
+
const entry = group.steps.find((s) => s.index === index);
|
|
256
|
+
if (!entry) continue;
|
|
257
|
+
const { oldContent, newContent } = entry.step;
|
|
258
|
+
if (!oldContent || !newContent) return;
|
|
259
|
+
const currentContent = codeBlockService.getCodeContentById(id);
|
|
260
|
+
diffDialogRef.value?.open({
|
|
261
|
+
category: 'code-block',
|
|
262
|
+
lastValue: oldContent as Record<string, any>,
|
|
263
|
+
value: newContent as Record<string, any>,
|
|
264
|
+
currentValue: (currentContent as Record<string, any>) || null,
|
|
265
|
+
targetLabel: newContent.name || oldContent.name || `${id}`,
|
|
266
|
+
id,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li
|
|
3
|
+
class="m-editor-history-list-item m-editor-history-list-initial"
|
|
4
|
+
:class="{ 'is-current': isCurrent, 'is-clickable': !isCurrent }"
|
|
5
|
+
:title="isCurrent ? '当前已回到未修改的初始状态' : '点击回到未修改的初始状态'"
|
|
6
|
+
@click="onClick"
|
|
7
|
+
>
|
|
8
|
+
<span class="m-editor-history-list-item-index" title="历史步骤编号 #0(未修改的初始状态)">#0</span>
|
|
9
|
+
<span class="m-editor-history-list-item-op op-initial">初始</span>
|
|
10
|
+
<span class="m-editor-history-list-item-desc">未修改的初始状态</span>
|
|
11
|
+
<span v-if="isCurrent" class="m-editor-history-list-item-current">当前</span>
|
|
12
|
+
</li>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script lang="ts" setup>
|
|
16
|
+
/**
|
|
17
|
+
* 「初始状态」记录行:渲染于历史列表底部,作为整个栈的"零点"。
|
|
18
|
+
* - 点击该行会把对应栈撤销到 cursor === 0(即没有任何已应用步骤),等同于回到所有修改之前。
|
|
19
|
+
* - 当对应栈本身已处于 cursor === 0 时(isCurrent=true),用户已在初始状态,点击不再触发动作。
|
|
20
|
+
*
|
|
21
|
+
* 该行不是真实 step,仅作为 UI 入口;上层负责把"点击"翻译为 `service.goto*(0)`。
|
|
22
|
+
*/
|
|
23
|
+
defineOptions({
|
|
24
|
+
name: 'MEditorHistoryListInitialRow',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const props = defineProps<{
|
|
28
|
+
/** 当前对应栈是否已经处于初始状态 (cursor === 0)。true 时用蓝条高亮并禁用点击。 */
|
|
29
|
+
isCurrent: boolean;
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits<{
|
|
33
|
+
/** 点击非当前的初始项时触发,由上层调用对应 service 的 goto 把 cursor 移到 0。 */
|
|
34
|
+
(_e: 'goto-initial'): void;
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const onClick = () => {
|
|
38
|
+
if (props.isCurrent) return;
|
|
39
|
+
emit('goto-initial');
|
|
40
|
+
};
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="!list.length" class="m-editor-history-list-empty">暂无操作记录</div>
|
|
3
|
+
<TMagicScrollbar v-else max-height="360px">
|
|
4
|
+
<ul class="m-editor-history-list-ul">
|
|
5
|
+
<GroupRow
|
|
6
|
+
v-for="(group, gIdx) in list"
|
|
7
|
+
:key="`pg-${gIdx}`"
|
|
8
|
+
:group-key="`pg-${gIdx}`"
|
|
9
|
+
:applied="group.applied"
|
|
10
|
+
:merged="group.steps.length > 1"
|
|
11
|
+
:op-type="group.opType"
|
|
12
|
+
:desc="describePageGroup(group)"
|
|
13
|
+
:step-count="group.steps.length"
|
|
14
|
+
:sub-steps="
|
|
15
|
+
group.steps.map((s) => ({
|
|
16
|
+
index: s.index,
|
|
17
|
+
applied: s.applied,
|
|
18
|
+
isCurrent: s.isCurrent,
|
|
19
|
+
desc: describePageStep(s.step),
|
|
20
|
+
diffable: isPageStepDiffable(s.step),
|
|
21
|
+
revertable: s.applied,
|
|
22
|
+
}))
|
|
23
|
+
"
|
|
24
|
+
:is-current="group.isCurrent"
|
|
25
|
+
:expanded="!!expanded[`pg-${gIdx}`]"
|
|
26
|
+
@toggle="(key: string) => $emit('toggle', key)"
|
|
27
|
+
@goto="(index: number) => $emit('goto', index)"
|
|
28
|
+
@diff-step="(index: number) => $emit('diff-step', index)"
|
|
29
|
+
@revert-step="(index: number) => $emit('revert-step', index)"
|
|
30
|
+
/>
|
|
31
|
+
<!--
|
|
32
|
+
初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),
|
|
33
|
+
作为"未修改"零点。当所有 group 都未 applied 时它即为当前位置。
|
|
34
|
+
-->
|
|
35
|
+
<InitialRow :is-current="isInitial" @goto-initial="$emit('goto-initial')" />
|
|
36
|
+
</ul>
|
|
37
|
+
</TMagicScrollbar>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script lang="ts" setup>
|
|
41
|
+
import { computed } from 'vue';
|
|
42
|
+
|
|
43
|
+
import { TMagicScrollbar } from '@tmagic/design';
|
|
44
|
+
|
|
45
|
+
import type { PageHistoryGroup, StepValue } from '@editor/type';
|
|
46
|
+
|
|
47
|
+
import { describePageGroup, describePageStep } from './composables';
|
|
48
|
+
import GroupRow from './GroupRow.vue';
|
|
49
|
+
import InitialRow from './InitialRow.vue';
|
|
50
|
+
|
|
51
|
+
defineOptions({
|
|
52
|
+
name: 'MEditorHistoryListPageTab',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const props = defineProps<{
|
|
56
|
+
/** 当前活动页面的历史分组列表,已按时间倒序排好(最新一组在最前)。空数组时显示空态。 */
|
|
57
|
+
list: PageHistoryGroup[];
|
|
58
|
+
/** 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。本 tab 使用 `pg-${idx}` 作为 key。 */
|
|
59
|
+
expanded: Record<string, boolean>;
|
|
60
|
+
}>();
|
|
61
|
+
|
|
62
|
+
defineEmits<{
|
|
63
|
+
/** 透传 GroupRow 的 toggle 事件给上层 panel,由其更新 expanded。 */
|
|
64
|
+
(_e: 'toggle', _key: string): void;
|
|
65
|
+
/** 透传 GroupRow 的 goto 事件,携带目标 step 在栈中的索引。 */
|
|
66
|
+
(_e: 'goto', _index: number): void;
|
|
67
|
+
/** 用户点击初始项希望回到未修改的状态(cursor=0)。 */
|
|
68
|
+
(_e: 'goto-initial'): void;
|
|
69
|
+
/** 用户点击"查看差异"按钮,携带目标 step 在栈中的索引。 */
|
|
70
|
+
(_e: 'diff-step', _index: number): void;
|
|
71
|
+
/** 用户点击"回滚"按钮,携带目标 step 在栈中的索引,类 git revert。 */
|
|
72
|
+
(_e: 'revert-step', _index: number): void;
|
|
73
|
+
}>();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 当前 step 是否可查看差异:
|
|
77
|
+
* - 仅 update 操作;
|
|
78
|
+
* - 单节点更新(updatedItems.length === 1),且 oldNode / newNode 都存在。
|
|
79
|
+
* 多节点更新难以选定单一对比目标,统一不展示差异入口。
|
|
80
|
+
*/
|
|
81
|
+
const isPageStepDiffable = (step: StepValue): boolean => {
|
|
82
|
+
if (step.opType !== 'update') return false;
|
|
83
|
+
const items = step.updatedItems ?? [];
|
|
84
|
+
if (items.length !== 1) return false;
|
|
85
|
+
return Boolean(items[0]?.oldNode && items[0]?.newNode);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 是否处于"初始状态"——即对应页面历史栈 cursor===0:
|
|
90
|
+
* 当 list 中所有 group 的 applied 都为 false 时即为该状态。
|
|
91
|
+
* 没有任何 group 的情况由外层"暂无操作记录"分支兜底,本计算可以不考虑。
|
|
92
|
+
*/
|
|
93
|
+
const isInitial = computed(() => props.list.length > 0 && props.list.every((g) => !g.applied));
|
|
94
|
+
</script>
|