@tmagic/editor 1.8.0-beta.4 → 1.8.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/es/index.js +6 -3
- package/dist/es/initService.js +1 -1
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -55
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -39
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +149 -103
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +32 -8
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +333 -211
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -60
- package/dist/es/layouts/history-list/composables.js +73 -32
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/services/codeBlock.js +85 -37
- package/dist/es/services/dataSource.js +62 -26
- package/dist/es/services/editor.js +243 -100
- package/dist/es/services/history.js +270 -206
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +49 -6
- package/dist/es/utils/editor.js +35 -1
- package/dist/es/utils/history.js +223 -0
- package/dist/es/utils/indexed-db.js +86 -0
- package/dist/es/utils/undo-redo.js +60 -1
- package/dist/style.css +49 -6
- package/dist/tmagic-editor.umd.cjs +1799 -905
- package/package.json +7 -7
- package/src/components/CompareForm.vue +3 -1
- package/src/components/ToolButton.vue +2 -2
- package/src/index.ts +1 -0
- package/src/initService.ts +1 -1
- package/src/layouts/Framework.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +34 -71
- package/src/layouts/history-list/BucketTab.vue +46 -54
- package/src/layouts/history-list/GroupRow.vue +146 -111
- package/src/layouts/history-list/HistoryDiffDialog.vue +94 -68
- package/src/layouts/history-list/HistoryListPanel.vue +296 -113
- package/src/layouts/history-list/InitialRow.vue +25 -9
- package/src/layouts/history-list/PageTab.vue +57 -51
- package/src/layouts/history-list/composables.ts +157 -36
- package/src/layouts/page-bar/PageBar.vue +4 -2
- package/src/layouts/sidebar/Sidebar.vue +6 -1
- package/src/services/codeBlock.ts +107 -37
- package/src/services/dataSource.ts +89 -40
- package/src/services/editor.ts +370 -134
- package/src/services/history.ts +305 -203
- package/src/services/ui.ts +7 -0
- package/src/theme/history-list-panel.scss +72 -5
- package/src/theme/page-bar.scss +0 -4
- package/src/type.ts +167 -63
- package/src/utils/editor.ts +41 -1
- package/src/utils/history.ts +298 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/indexed-db.ts +122 -0
- package/src/utils/undo-redo.ts +88 -0
- package/types/index.d.ts +783 -291
|
@@ -1,46 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="!list.length" class="m-editor-history-list-empty">暂无操作记录</div>
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
desc: describePageStep(s.step),
|
|
23
|
-
diffable: isPageStepDiffable(s.step),
|
|
24
|
-
revertable: s.applied && isPageStepRevertable(s.step),
|
|
25
|
-
source: s.step.source,
|
|
26
|
-
time: formatHistoryTime(s.step.timestamp),
|
|
27
|
-
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
|
28
|
-
}))
|
|
29
|
-
"
|
|
30
|
-
:is-current="group.isCurrent"
|
|
31
|
-
:expanded="!!expanded[`pg-${group.steps[0]?.index}`]"
|
|
32
|
-
@toggle="(key: string) => $emit('toggle', key)"
|
|
33
|
-
@goto="(index: number) => $emit('goto', index)"
|
|
34
|
-
@diff-step="(index: number) => $emit('diff-step', index)"
|
|
35
|
-
@revert-step="(index: number) => $emit('revert-step', index)"
|
|
36
|
-
/>
|
|
37
|
-
<!--
|
|
2
|
+
<div v-if="!list.length && !marker" class="m-editor-history-list-empty">暂无操作记录</div>
|
|
3
|
+
<template v-else>
|
|
4
|
+
<div v-if="list.length" class="m-editor-history-list-toolbar">
|
|
5
|
+
<span class="m-editor-history-list-clear" title="清空当前页面的历史记录" @click="$emit('clear')">清空</span>
|
|
6
|
+
</div>
|
|
7
|
+
<TMagicScrollbar max-height="360px">
|
|
8
|
+
<ul class="m-editor-history-list-ul">
|
|
9
|
+
<GroupRow
|
|
10
|
+
v-for="group in list"
|
|
11
|
+
:key="rowKey(group)"
|
|
12
|
+
:group="toRow(group)"
|
|
13
|
+
:expanded="isHistoryGroupExpanded(expanded, rowKey(group))"
|
|
14
|
+
:select-enabled="true"
|
|
15
|
+
@toggle="(key: string) => $emit('toggle', key)"
|
|
16
|
+
@goto="(index: number) => $emit('goto', index)"
|
|
17
|
+
@diff-step="(index: number) => $emit('diff-step', index)"
|
|
18
|
+
@revert-step="(index: number) => $emit('revert-step', index)"
|
|
19
|
+
@select="(index: number) => $emit('select', index)"
|
|
20
|
+
/>
|
|
21
|
+
<!--
|
|
38
22
|
初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),
|
|
39
23
|
作为"未修改"零点。当所有 group 都未 applied 时它即为当前位置。
|
|
24
|
+
设置 root 时生成的「未修改的初始状态」标记(marker)会作为该行的文案与时间来源。
|
|
40
25
|
-->
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
<InitialRow :is-current="isInitial" :marker="marker" @goto-initial="$emit('goto-initial')" />
|
|
27
|
+
</ul>
|
|
28
|
+
</TMagicScrollbar>
|
|
29
|
+
</template>
|
|
44
30
|
</template>
|
|
45
31
|
|
|
46
32
|
<script lang="ts" setup>
|
|
@@ -48,16 +34,15 @@ import { computed } from 'vue';
|
|
|
48
34
|
|
|
49
35
|
import { TMagicScrollbar } from '@tmagic/design';
|
|
50
36
|
|
|
51
|
-
import type { PageHistoryGroup, StepValue } from '@editor/type';
|
|
37
|
+
import type { HistoryRowDescriptor, PageHistoryGroup, StepValue } from '@editor/type';
|
|
52
38
|
|
|
39
|
+
import type { HistoryRowGroup } from './composables';
|
|
53
40
|
import {
|
|
54
41
|
describePageGroup,
|
|
55
42
|
describePageStep,
|
|
56
|
-
|
|
57
|
-
formatHistoryTime,
|
|
58
|
-
groupSource,
|
|
59
|
-
groupTimestamp,
|
|
43
|
+
isHistoryGroupExpanded,
|
|
60
44
|
isPageStepRevertable,
|
|
45
|
+
toRowGroup,
|
|
61
46
|
} from './composables';
|
|
62
47
|
import GroupRow from './GroupRow.vue';
|
|
63
48
|
import InitialRow from './InitialRow.vue';
|
|
@@ -70,11 +55,16 @@ const props = defineProps<{
|
|
|
70
55
|
/** 当前活动页面的历史分组列表,已按时间倒序排好(最新一组在最前)。空数组时显示空态。 */
|
|
71
56
|
list: PageHistoryGroup[];
|
|
72
57
|
/**
|
|
73
|
-
* 共享的折叠状态表(key ->
|
|
58
|
+
* 共享的折叠状态表(key -> 是否展开,缺省或 true 为展开、false 为收起),由顶层 panel 统一维护。
|
|
74
59
|
* 本 tab 使用 `pg-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
|
|
75
60
|
* 这样历史数据更新(新增 / 撤销重做导致列表顺序变化)后,已展开的分组状态仍能正确保持。
|
|
76
61
|
*/
|
|
77
62
|
expanded: Record<string, boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* 当前活动页的「加载/初始」基线记录(设置 root 时生成的 `opType: 'initial'` StepValue)。
|
|
65
|
+
* 提供时即使没有任何操作记录也会展示底部初始行,并用其文案 / 时间渲染。
|
|
66
|
+
*/
|
|
67
|
+
marker?: StepValue;
|
|
78
68
|
}>();
|
|
79
69
|
|
|
80
70
|
defineEmits<{
|
|
@@ -88,25 +78,41 @@ defineEmits<{
|
|
|
88
78
|
(_e: 'diff-step', _index: number): void;
|
|
89
79
|
/** 用户点击"回滚"按钮,携带目标 step 在栈中的索引,类 git revert。 */
|
|
90
80
|
(_e: 'revert-step', _index: number): void;
|
|
81
|
+
/** 用户点击记录行希望选中对应节点,携带目标 step 在栈中的索引。 */
|
|
82
|
+
(_e: 'select', _index: number): void;
|
|
83
|
+
/** 用户点击"清空"按钮,请求清空当前页面的历史记录(由上层弹窗二次确认后执行)。 */
|
|
84
|
+
(_e: 'clear'): void;
|
|
91
85
|
}>();
|
|
92
86
|
|
|
93
87
|
/**
|
|
94
88
|
* 当前 step 是否可查看差异:
|
|
95
89
|
* - 仅 update 操作;
|
|
96
|
-
* - 单节点更新(
|
|
90
|
+
* - 单节点更新(diff.length === 1),且 oldSchema / newSchema 都存在。
|
|
97
91
|
* 多节点更新难以选定单一对比目标,统一不展示差异入口。
|
|
98
92
|
*/
|
|
99
93
|
const isPageStepDiffable = (step: StepValue): boolean => {
|
|
100
94
|
if (step.opType !== 'update') return false;
|
|
101
|
-
const items = step.
|
|
95
|
+
const items = step.diff ?? [];
|
|
102
96
|
if (items.length !== 1) return false;
|
|
103
|
-
return Boolean(items[0]?.
|
|
97
|
+
return Boolean(items[0]?.oldSchema && items[0]?.newSchema);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** 页面历史的描述 / 可操作性判定集合,注入给统一的 `toRowGroup`。 */
|
|
101
|
+
const descriptor: HistoryRowDescriptor<StepValue> = {
|
|
102
|
+
describeGroup: describePageGroup,
|
|
103
|
+
describeStep: describePageStep,
|
|
104
|
+
isStepDiffable: isPageStepDiffable,
|
|
105
|
+
isStepRevertable: isPageStepRevertable,
|
|
104
106
|
};
|
|
105
107
|
|
|
108
|
+
const rowKey = (group: PageHistoryGroup) => `pg-${group.steps[0]?.index}`;
|
|
109
|
+
|
|
110
|
+
const toRow = (group: PageHistoryGroup): HistoryRowGroup => toRowGroup(group, rowKey(group), descriptor);
|
|
111
|
+
|
|
106
112
|
/**
|
|
107
113
|
* 是否处于"初始状态"——即对应页面历史栈 cursor===0:
|
|
108
|
-
* 当 list 中所有 group 的 applied 都为 false
|
|
109
|
-
*
|
|
114
|
+
* 当 list 中所有 group 的 applied 都为 false 时即为该状态(空列表 `every` 返回 true,
|
|
115
|
+
* 即仅有 marker、无任何操作记录时也视为处于初始状态)。
|
|
110
116
|
*/
|
|
111
|
-
const isInitial = computed(() => props.list.
|
|
117
|
+
const isInitial = computed(() => props.list.every((g) => !g.applied));
|
|
112
118
|
</script>
|
|
@@ -4,16 +4,85 @@ import { datetimeFormatter } from '@tmagic/form';
|
|
|
4
4
|
|
|
5
5
|
import { useServices } from '@editor/hooks/use-services';
|
|
6
6
|
import type {
|
|
7
|
+
BaseStepValue,
|
|
7
8
|
CodeBlockHistoryGroup,
|
|
8
9
|
CodeBlockStepValue,
|
|
9
10
|
DataSourceHistoryGroup,
|
|
10
11
|
DataSourceStepValue,
|
|
11
12
|
HistoryOpSource,
|
|
12
13
|
HistoryOpType,
|
|
14
|
+
HistoryRowDescriptor,
|
|
13
15
|
PageHistoryGroup,
|
|
14
16
|
StepValue,
|
|
15
17
|
} from '@editor/type';
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 通用 bucket 分组(数据源 / 代码块及业务自定义历史)在面板中的展示结构。
|
|
21
|
+
* 由 Bucket / BucketTab 复用,step 类型通过泛型 T 收窄(约束为 {@link BaseStepValue})。
|
|
22
|
+
*/
|
|
23
|
+
export interface HistoryBucketGroup<T extends BaseStepValue = BaseStepValue> {
|
|
24
|
+
/** 组内最后一步是否已应用 */
|
|
25
|
+
applied: boolean;
|
|
26
|
+
/** 是否为当前所在的分组 */
|
|
27
|
+
isCurrent?: boolean;
|
|
28
|
+
/** 该分组的操作类型 */
|
|
29
|
+
opType: HistoryOpType;
|
|
30
|
+
/** 组内所有步骤 */
|
|
31
|
+
steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step)。 */
|
|
35
|
+
export interface HistoryRowStep {
|
|
36
|
+
/** 该子步在所属栈中的稳定索引。 */
|
|
37
|
+
index: number;
|
|
38
|
+
/** 是否已应用(false 表示已被 undo,UI 灰态)。 */
|
|
39
|
+
applied: boolean;
|
|
40
|
+
/** 是否为当前所在步骤。 */
|
|
41
|
+
isCurrent?: boolean;
|
|
42
|
+
/** 是否为最近一次保存的记录。 */
|
|
43
|
+
saved?: boolean;
|
|
44
|
+
/** 子步描述文案。 */
|
|
45
|
+
desc: string;
|
|
46
|
+
/** 是否可查看差异。 */
|
|
47
|
+
diffable?: boolean;
|
|
48
|
+
/** 是否可回滚。 */
|
|
49
|
+
revertable?: boolean;
|
|
50
|
+
/** 操作途径。 */
|
|
51
|
+
source?: HistoryOpSource;
|
|
52
|
+
/** 时间文案。 */
|
|
53
|
+
time?: string;
|
|
54
|
+
/** 时间的完整 title 提示。 */
|
|
55
|
+
timeTitle?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* GroupRow 渲染所需的整组视图模型(由 {@link toRowGroup} 统一派生)。
|
|
60
|
+
* 把原先 GroupRow 上十多个扁平 props 收敛为单一对象,header 信息与子步列表一并携带。
|
|
61
|
+
*/
|
|
62
|
+
export interface HistoryRowGroup {
|
|
63
|
+
/** 分组的稳定 key,作为 toggle 事件 payload 与折叠状态的索引。 */
|
|
64
|
+
key: string;
|
|
65
|
+
/** 组内最后一步是否已应用。 */
|
|
66
|
+
applied: boolean;
|
|
67
|
+
/** 是否为当前所在分组。 */
|
|
68
|
+
isCurrent: boolean;
|
|
69
|
+
/** 操作类型,用于徽标颜色与文案。 */
|
|
70
|
+
opType: HistoryOpType;
|
|
71
|
+
/** 组整体描述文案。 */
|
|
72
|
+
desc: string;
|
|
73
|
+
/** 组的操作途径(取组内最近一步)。 */
|
|
74
|
+
source?: HistoryOpSource;
|
|
75
|
+
/** 组头部时间文案(取组内最近一步)。 */
|
|
76
|
+
time?: string;
|
|
77
|
+
/** 组头部时间的完整 title 提示。 */
|
|
78
|
+
timeTitle?: string;
|
|
79
|
+
/** 子步列表(时间正序);其长度即合并步数,length > 1 即为合并组。 */
|
|
80
|
+
subSteps: HistoryRowStep[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
|
|
84
|
+
export const isHistoryGroupExpanded = (expanded: Record<string, boolean>, key: string) => expanded[key] !== false;
|
|
85
|
+
|
|
17
86
|
/**
|
|
18
87
|
* 历史记录面板共享逻辑:
|
|
19
88
|
* - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
|
|
@@ -28,10 +97,11 @@ export const useHistoryList = () => {
|
|
|
28
97
|
/**
|
|
29
98
|
* 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
|
|
30
99
|
* 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
|
|
100
|
+
* 合并组默认展开;仅当值为 `false` 时表示收起。
|
|
31
101
|
*/
|
|
32
102
|
const expanded = reactive<Record<string, boolean>>({});
|
|
33
103
|
const toggleGroup = (key: string) => {
|
|
34
|
-
expanded[key] =
|
|
104
|
+
expanded[key] = expanded[key] === false;
|
|
35
105
|
};
|
|
36
106
|
|
|
37
107
|
const pageGroups = computed(() => historyService.getPageHistoryGroups());
|
|
@@ -115,6 +185,7 @@ const HISTORY_SOURCE_LABELS: Record<string, string> = {
|
|
|
115
185
|
'component-panel': '组件面板',
|
|
116
186
|
props: '配置面板',
|
|
117
187
|
code: '源码',
|
|
188
|
+
'root-code': 'DSL源码',
|
|
118
189
|
'stage-contextmenu': '画布菜单',
|
|
119
190
|
'tree-contextmenu': '树菜单',
|
|
120
191
|
toolbar: '工具栏',
|
|
@@ -122,6 +193,8 @@ const HISTORY_SOURCE_LABELS: Record<string, string> = {
|
|
|
122
193
|
rollback: '回滚',
|
|
123
194
|
api: '接口',
|
|
124
195
|
ai: 'AI',
|
|
196
|
+
initial: '初始值',
|
|
197
|
+
sync: '同步',
|
|
125
198
|
unknown: '未知',
|
|
126
199
|
};
|
|
127
200
|
|
|
@@ -134,7 +207,51 @@ export const sourceLabel = (source: HistoryOpSource = 'unknown'): string => {
|
|
|
134
207
|
export const groupSource = (group: { steps: { step: { source?: HistoryOpSource } }[] }): HistoryOpSource | undefined =>
|
|
135
208
|
group.steps[group.steps.length - 1]?.step.source;
|
|
136
209
|
|
|
137
|
-
|
|
210
|
+
/** {@link toRowGroup} 接受的最小分组结构,PageHistoryGroup 与 HistoryBucketGroup 均满足。 */
|
|
211
|
+
interface RowGroupInput<T extends BaseStepValue = BaseStepValue> {
|
|
212
|
+
applied: boolean;
|
|
213
|
+
isCurrent?: boolean;
|
|
214
|
+
opType: HistoryOpType;
|
|
215
|
+
steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 把一个历史分组(页面 / bucket)派生为 GroupRow 直接消费的视图模型 {@link HistoryRowGroup}。
|
|
220
|
+
* 统一了原先 PageTab / Bucket 各自内联的 sub-steps 映射逻辑:描述、可差异、可回滚、时间、途径
|
|
221
|
+
* 全部在此一次性算好,组件层只负责渲染。
|
|
222
|
+
*/
|
|
223
|
+
export const toRowGroup = <T extends BaseStepValue = BaseStepValue>(
|
|
224
|
+
group: RowGroupInput<T>,
|
|
225
|
+
key: string,
|
|
226
|
+
descriptor: HistoryRowDescriptor<T>,
|
|
227
|
+
): HistoryRowGroup => {
|
|
228
|
+
const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
|
|
229
|
+
const timestamp = groupTimestamp(group);
|
|
230
|
+
return {
|
|
231
|
+
key,
|
|
232
|
+
applied: group.applied,
|
|
233
|
+
isCurrent: Boolean(group.isCurrent),
|
|
234
|
+
opType: group.opType,
|
|
235
|
+
desc: describeGroup(group),
|
|
236
|
+
source: groupSource(group),
|
|
237
|
+
time: formatHistoryTime(timestamp),
|
|
238
|
+
timeTitle: formatHistoryFullTime(timestamp),
|
|
239
|
+
subSteps: group.steps.map((s) => ({
|
|
240
|
+
index: s.index,
|
|
241
|
+
applied: s.applied,
|
|
242
|
+
isCurrent: s.isCurrent,
|
|
243
|
+
saved: s.step.saved,
|
|
244
|
+
desc: describeStep(s.step),
|
|
245
|
+
diffable: isStepDiffable ? isStepDiffable(s.step) : false,
|
|
246
|
+
revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
|
|
247
|
+
source: s.step.source,
|
|
248
|
+
time: formatHistoryTime(s.step.timestamp),
|
|
249
|
+
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
|
250
|
+
})),
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const nameOf = (node?: { name?: string; id?: string | number; type?: string }) =>
|
|
138
255
|
node?.name || node?.type || `${node?.id ?? ''}`;
|
|
139
256
|
|
|
140
257
|
/**
|
|
@@ -159,25 +276,25 @@ const pickLastDescription = (descs: (string | undefined)[]): string | undefined
|
|
|
159
276
|
export const describePageStep = (step: StepValue) => {
|
|
160
277
|
if (step.historyDescription) return step.historyDescription;
|
|
161
278
|
const { opType } = step;
|
|
279
|
+
const items = step.diff ?? [];
|
|
162
280
|
if (opType === 'add') {
|
|
163
|
-
const count =
|
|
164
|
-
const node =
|
|
281
|
+
const count = items.length;
|
|
282
|
+
const node = items[0]?.newSchema;
|
|
165
283
|
return `新增 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ''}`;
|
|
166
284
|
}
|
|
167
285
|
if (opType === 'remove') {
|
|
168
|
-
const count =
|
|
169
|
-
const node =
|
|
286
|
+
const count = items.length;
|
|
287
|
+
const node = items[0]?.oldSchema;
|
|
170
288
|
return `删除 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ''}`;
|
|
171
289
|
}
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
const { newNode, changeRecords } = updated[0];
|
|
290
|
+
if (!items.length) return '修改节点';
|
|
291
|
+
if (items.length === 1) {
|
|
292
|
+
const { newSchema, changeRecords } = items[0];
|
|
176
293
|
const propPath = changeRecords?.[0]?.propPath;
|
|
177
|
-
const target = labelWithId(nameOf(
|
|
294
|
+
const target = labelWithId(nameOf(newSchema), newSchema?.id);
|
|
178
295
|
return `修改 ${target}${propPath ? ` · ${propPath}` : ''}`;
|
|
179
296
|
}
|
|
180
|
-
return `修改 ${
|
|
297
|
+
return `修改 ${items.length} 个节点`;
|
|
181
298
|
};
|
|
182
299
|
|
|
183
300
|
/**
|
|
@@ -192,7 +309,7 @@ export const describePageGroup = (group: PageHistoryGroup) => {
|
|
|
192
309
|
if (group.steps.length === 1) return describePageStep(group.steps[0].step);
|
|
193
310
|
const paths = new Set<string>();
|
|
194
311
|
group.steps.forEach((s) => {
|
|
195
|
-
s.step.
|
|
312
|
+
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
196
313
|
});
|
|
197
314
|
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
198
315
|
const target = labelWithId(
|
|
@@ -204,12 +321,11 @@ export const describePageGroup = (group: PageHistoryGroup) => {
|
|
|
204
321
|
|
|
205
322
|
export const describeDataSourceStep = (step: DataSourceStepValue) => {
|
|
206
323
|
if (step.historyDescription) return step.historyDescription;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const title = labelWithId(step.newSchema?.title || step.oldSchema?.title, step.id);
|
|
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);
|
|
213
329
|
return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
|
|
214
330
|
};
|
|
215
331
|
|
|
@@ -219,22 +335,23 @@ export const describeDataSourceGroup = (group: DataSourceHistoryGroup) => {
|
|
|
219
335
|
if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
|
|
220
336
|
const paths = new Set<string>();
|
|
221
337
|
group.steps.forEach((s) => {
|
|
222
|
-
s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
338
|
+
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
223
339
|
});
|
|
224
340
|
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
225
|
-
const rawTitle =
|
|
341
|
+
const rawTitle =
|
|
342
|
+
group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.title ||
|
|
343
|
+
group.steps[0].step.diff?.[0]?.oldSchema?.title;
|
|
226
344
|
const target = labelWithId(rawTitle, group.id);
|
|
227
345
|
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
228
346
|
};
|
|
229
347
|
|
|
230
348
|
export const describeCodeBlockStep = (step: CodeBlockStepValue) => {
|
|
231
349
|
if (step.historyDescription) return step.historyDescription;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
const title = labelWithId(step.newContent?.name || step.oldContent?.name, step.id);
|
|
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);
|
|
238
355
|
return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
|
|
239
356
|
};
|
|
240
357
|
|
|
@@ -244,10 +361,12 @@ export const describeCodeBlockGroup = (group: CodeBlockHistoryGroup) => {
|
|
|
244
361
|
if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
|
|
245
362
|
const paths = new Set<string>();
|
|
246
363
|
group.steps.forEach((s) => {
|
|
247
|
-
s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
364
|
+
s.step.diff?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
|
|
248
365
|
});
|
|
249
366
|
const pathList = Array.from(paths).slice(0, 3).join(', ');
|
|
250
|
-
const rawName =
|
|
367
|
+
const rawName =
|
|
368
|
+
group.steps[group.steps.length - 1].step.diff?.[0]?.newSchema?.name ||
|
|
369
|
+
group.steps[0].step.diff?.[0]?.oldSchema?.name;
|
|
251
370
|
const target = labelWithId(rawName, group.id);
|
|
252
371
|
return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
|
|
253
372
|
};
|
|
@@ -260,27 +379,29 @@ export const describeCodeBlockGroup = (group: CodeBlockHistoryGroup) => {
|
|
|
260
379
|
*/
|
|
261
380
|
export const isPageStepRevertable = (step: StepValue): boolean => {
|
|
262
381
|
if (step.opType !== 'update') return true;
|
|
263
|
-
const items = step.
|
|
382
|
+
const items = step.diff ?? [];
|
|
264
383
|
if (!items.length) return false;
|
|
265
384
|
return items.every((item) => Boolean(item.changeRecords?.length));
|
|
266
385
|
};
|
|
267
386
|
|
|
268
387
|
/**
|
|
269
388
|
* 数据源 step 是否支持「回滚」:
|
|
270
|
-
* -
|
|
389
|
+
* - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
|
|
271
390
|
* - 更新(前后 schema 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
272
391
|
*/
|
|
273
392
|
export const isDataSourceStepRevertable = (step: DataSourceStepValue): boolean => {
|
|
274
|
-
|
|
275
|
-
|
|
393
|
+
const item = step.diff?.[0];
|
|
394
|
+
if (!item?.oldSchema || !item?.newSchema) return true;
|
|
395
|
+
return Boolean(item.changeRecords?.length);
|
|
276
396
|
};
|
|
277
397
|
|
|
278
398
|
/**
|
|
279
399
|
* 代码块 step 是否支持「回滚」:
|
|
280
|
-
* -
|
|
400
|
+
* - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
|
|
281
401
|
* - 更新(前后 content 都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
282
402
|
*/
|
|
283
403
|
export const isCodeBlockStepRevertable = (step: CodeBlockStepValue): boolean => {
|
|
284
|
-
|
|
285
|
-
|
|
404
|
+
const item = step.diff?.[0];
|
|
405
|
+
if (!item?.oldSchema || !item?.newSchema) return true;
|
|
406
|
+
return Boolean(item.changeRecords?.length);
|
|
286
407
|
};
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
type: 'button',
|
|
52
52
|
text: '删除',
|
|
53
53
|
icon: Delete,
|
|
54
|
+
buttonProps: { type: 'danger' },
|
|
54
55
|
handler: () => remove(item),
|
|
55
56
|
}"
|
|
56
57
|
></ToolButton>
|
|
@@ -72,7 +73,7 @@ import { computed, ref, useTemplateRef, watch } from 'vue';
|
|
|
72
73
|
import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
|
|
73
74
|
|
|
74
75
|
import { type Id, type MPage, type MPageFragment, NodeType } from '@tmagic/core';
|
|
75
|
-
import { TMagicIcon, TMagicPopover } from '@tmagic/design';
|
|
76
|
+
import { TMagicIcon, tMagicMessageBox, TMagicPopover } from '@tmagic/design';
|
|
76
77
|
|
|
77
78
|
import ToolButton from '@editor/components/ToolButton.vue';
|
|
78
79
|
import { useServices } from '@editor/hooks/use-services';
|
|
@@ -140,7 +141,8 @@ const copy = (node: MPage | MPageFragment) => {
|
|
|
140
141
|
});
|
|
141
142
|
};
|
|
142
143
|
|
|
143
|
-
const remove = (node: MPage | MPageFragment) => {
|
|
144
|
+
const remove = async (node: MPage | MPageFragment) => {
|
|
145
|
+
await tMagicMessageBox.confirm('确定删除该页面吗?');
|
|
144
146
|
editorService.remove(node);
|
|
145
147
|
};
|
|
146
148
|
|
|
@@ -239,7 +239,12 @@ const unWatchEditorContentHeight = watch(
|
|
|
239
239
|
},
|
|
240
240
|
);
|
|
241
241
|
|
|
242
|
-
const activeTabName =
|
|
242
|
+
const activeTabName = computed<string>({
|
|
243
|
+
get: () => uiService.get('sideBarActiveTabName'),
|
|
244
|
+
set: (value) => uiService.set('sideBarActiveTabName', value),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
uiService.set('sideBarActiveTabName', props.data?.status || '');
|
|
243
248
|
|
|
244
249
|
const getItemConfig = (data: SideItem): SideComponent => {
|
|
245
250
|
const map: Record<string, SideComponent> = {
|