@tmagic/editor 1.8.0-beta.1 → 1.8.0-beta.3

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.
Files changed (119) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +20 -2
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
  4. package/dist/es/components/CompareForm.js +5 -0
  5. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +171 -0
  6. package/dist/es/editorProps.js +2 -0
  7. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
  8. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
  9. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
  10. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
  11. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  12. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +2 -2
  13. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
  14. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
  15. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
  16. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
  17. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  18. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
  19. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
  20. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
  21. package/dist/es/hooks/use-code-block-edit.js +2 -2
  22. package/dist/es/hooks/use-stage.js +17 -8
  23. package/dist/es/index.js +5 -1
  24. package/dist/es/initService.js +6 -6
  25. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +6 -5
  26. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
  27. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
  28. package/dist/es/layouts/history-list/Bucket.js +5 -0
  29. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +103 -0
  30. package/dist/es/layouts/history-list/BucketTab.js +5 -0
  31. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +73 -0
  32. package/dist/es/layouts/history-list/GroupRow.js +5 -0
  33. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +198 -0
  34. package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
  35. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +245 -0
  36. package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
  37. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +340 -0
  38. package/dist/es/layouts/history-list/InitialRow.js +5 -0
  39. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +54 -0
  40. package/dist/es/layouts/history-list/PageTab.js +5 -0
  41. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +105 -0
  42. package/dist/es/layouts/history-list/composables.js +172 -0
  43. package/dist/es/layouts/sidebar/layer/use-node-status.js +1 -1
  44. package/dist/es/services/BaseService.js +31 -68
  45. package/dist/es/services/codeBlock.js +205 -8
  46. package/dist/es/services/componentList.js +2 -2
  47. package/dist/es/services/dataSource.js +217 -8
  48. package/dist/es/services/dep.js +21 -9
  49. package/dist/es/services/editor.js +350 -160
  50. package/dist/es/services/events.js +2 -2
  51. package/dist/es/services/history.js +410 -12
  52. package/dist/es/services/keybinding.js +2 -2
  53. package/dist/es/services/props.js +8 -8
  54. package/dist/es/services/stageOverlay.js +2 -2
  55. package/dist/es/services/storage.js +2 -2
  56. package/dist/es/services/ui.js +2 -2
  57. package/dist/es/style.css +379 -1
  58. package/dist/es/utils/code-block.js +122 -0
  59. package/dist/es/utils/content-menu.js +4 -5
  60. package/dist/es/utils/dep/worker.js +1 -1
  61. package/dist/es/utils/editor.js +2 -2
  62. package/dist/es/utils/undo-redo.js +20 -0
  63. package/dist/style.css +379 -1
  64. package/dist/tmagic-editor.umd.cjs +3331 -787
  65. package/package.json +7 -7
  66. package/src/Editor.vue +15 -0
  67. package/src/components/CodeBlockEditor.vue +20 -109
  68. package/src/components/CodeParams.vue +6 -0
  69. package/src/components/CompareForm.vue +238 -0
  70. package/src/editorProps.ts +9 -0
  71. package/src/fields/Code.vue +19 -3
  72. package/src/fields/CodeLink.vue +2 -5
  73. package/src/fields/CodeSelect.vue +17 -1
  74. package/src/fields/CodeSelectCol.vue +30 -2
  75. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
  76. package/src/fields/DataSourceFieldSelect/Index.vue +2 -2
  77. package/src/fields/DataSourceFields.vue +12 -3
  78. package/src/fields/DataSourceMethodSelect.vue +4 -1
  79. package/src/fields/DataSourceMethods.vue +14 -4
  80. package/src/fields/DataSourceMocks.vue +13 -3
  81. package/src/fields/DataSourceSelect.vue +4 -1
  82. package/src/fields/EventSelect.vue +57 -8
  83. package/src/fields/KeyValue.vue +17 -4
  84. package/src/fields/UISelect.vue +5 -2
  85. package/src/hooks/use-code-block-edit.ts +5 -2
  86. package/src/hooks/use-stage.ts +24 -7
  87. package/src/index.ts +3 -0
  88. package/src/layouts/CodeEditor.vue +17 -5
  89. package/src/layouts/Framework.vue +10 -5
  90. package/src/layouts/NavMenu.vue +9 -0
  91. package/src/layouts/history-list/Bucket.vue +124 -0
  92. package/src/layouts/history-list/BucketTab.vue +77 -0
  93. package/src/layouts/history-list/GroupRow.vue +242 -0
  94. package/src/layouts/history-list/HistoryDiffDialog.vue +231 -0
  95. package/src/layouts/history-list/HistoryListPanel.vue +380 -0
  96. package/src/layouts/history-list/InitialRow.vue +52 -0
  97. package/src/layouts/history-list/PageTab.vue +108 -0
  98. package/src/layouts/history-list/composables.ts +226 -0
  99. package/src/layouts/sidebar/layer/use-node-status.ts +9 -6
  100. package/src/services/BaseService.ts +35 -86
  101. package/src/services/codeBlock.ts +256 -5
  102. package/src/services/dataSource.ts +257 -6
  103. package/src/services/dep.ts +32 -9
  104. package/src/services/editor.ts +454 -171
  105. package/src/services/history.ts +455 -8
  106. package/src/services/props.ts +2 -2
  107. package/src/theme/history-list-panel.scss +462 -0
  108. package/src/theme/theme.scss +1 -0
  109. package/src/type.ts +269 -6
  110. package/src/utils/code-block.ts +150 -0
  111. package/src/utils/content-menu.ts +8 -5
  112. package/src/utils/dep/worker.ts +5 -3
  113. package/src/utils/index.ts +1 -0
  114. package/src/utils/undo-redo.ts +23 -0
  115. package/types/index.d.ts +2274 -819
  116. package/dist/es/utils/compose.js +0 -44
  117. package/dist/es/utils/editor-history.js +0 -100
  118. package/src/utils/compose.ts +0 -52
  119. package/src/utils/editor-history.ts +0 -138
@@ -0,0 +1,108 @@
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 in list"
7
+ :key="`pg-${group.steps[0]?.index}`"
8
+ :group-key="`pg-${group.steps[0]?.index}`"
9
+ :applied="group.applied"
10
+ :merged="group.steps.length > 1"
11
+ :op-type="group.opType"
12
+ :desc="describePageGroup(group)"
13
+ :time="formatHistoryTime(groupTimestamp(group))"
14
+ :time-title="formatHistoryFullTime(groupTimestamp(group))"
15
+ :step-count="group.steps.length"
16
+ :sub-steps="
17
+ group.steps.map((s) => ({
18
+ index: s.index,
19
+ applied: s.applied,
20
+ isCurrent: s.isCurrent,
21
+ desc: describePageStep(s.step),
22
+ diffable: isPageStepDiffable(s.step),
23
+ revertable: s.applied,
24
+ time: formatHistoryTime(s.step.timestamp),
25
+ timeTitle: formatHistoryFullTime(s.step.timestamp),
26
+ }))
27
+ "
28
+ :is-current="group.isCurrent"
29
+ :expanded="!!expanded[`pg-${group.steps[0]?.index}`]"
30
+ @toggle="(key: string) => $emit('toggle', key)"
31
+ @goto="(index: number) => $emit('goto', index)"
32
+ @diff-step="(index: number) => $emit('diff-step', index)"
33
+ @revert-step="(index: number) => $emit('revert-step', index)"
34
+ />
35
+ <!--
36
+ 初始状态项:永远位于列表底部(页面 tab 倒序展示,最底部=最早),
37
+ 作为"未修改"零点。当所有 group 都未 applied 时它即为当前位置。
38
+ -->
39
+ <InitialRow :is-current="isInitial" @goto-initial="$emit('goto-initial')" />
40
+ </ul>
41
+ </TMagicScrollbar>
42
+ </template>
43
+
44
+ <script lang="ts" setup>
45
+ import { computed } from 'vue';
46
+
47
+ import { TMagicScrollbar } from '@tmagic/design';
48
+
49
+ import type { PageHistoryGroup, StepValue } from '@editor/type';
50
+
51
+ import {
52
+ describePageGroup,
53
+ describePageStep,
54
+ formatHistoryFullTime,
55
+ formatHistoryTime,
56
+ groupTimestamp,
57
+ } from './composables';
58
+ import GroupRow from './GroupRow.vue';
59
+ import InitialRow from './InitialRow.vue';
60
+
61
+ defineOptions({
62
+ name: 'MEditorHistoryListPageTab',
63
+ });
64
+
65
+ const props = defineProps<{
66
+ /** 当前活动页面的历史分组列表,已按时间倒序排好(最新一组在最前)。空数组时显示空态。 */
67
+ list: PageHistoryGroup[];
68
+ /**
69
+ * 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。
70
+ * 本 tab 使用 `pg-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
71
+ * 这样历史数据更新(新增 / 撤销重做导致列表顺序变化)后,已展开的分组状态仍能正确保持。
72
+ */
73
+ expanded: Record<string, boolean>;
74
+ }>();
75
+
76
+ defineEmits<{
77
+ /** 透传 GroupRow 的 toggle 事件给上层 panel,由其更新 expanded。 */
78
+ (_e: 'toggle', _key: string): void;
79
+ /** 透传 GroupRow 的 goto 事件,携带目标 step 在栈中的索引。 */
80
+ (_e: 'goto', _index: number): void;
81
+ /** 用户点击初始项希望回到未修改的状态(cursor=0)。 */
82
+ (_e: 'goto-initial'): void;
83
+ /** 用户点击"查看差异"按钮,携带目标 step 在栈中的索引。 */
84
+ (_e: 'diff-step', _index: number): void;
85
+ /** 用户点击"回滚"按钮,携带目标 step 在栈中的索引,类 git revert。 */
86
+ (_e: 'revert-step', _index: number): void;
87
+ }>();
88
+
89
+ /**
90
+ * 当前 step 是否可查看差异:
91
+ * - 仅 update 操作;
92
+ * - 单节点更新(updatedItems.length === 1),且 oldNode / newNode 都存在。
93
+ * 多节点更新难以选定单一对比目标,统一不展示差异入口。
94
+ */
95
+ const isPageStepDiffable = (step: StepValue): boolean => {
96
+ if (step.opType !== 'update') return false;
97
+ const items = step.updatedItems ?? [];
98
+ if (items.length !== 1) return false;
99
+ return Boolean(items[0]?.oldNode && items[0]?.newNode);
100
+ };
101
+
102
+ /**
103
+ * 是否处于"初始状态"——即对应页面历史栈 cursor===0:
104
+ * 当 list 中所有 group 的 applied 都为 false 时即为该状态。
105
+ * 没有任何 group 的情况由外层"暂无操作记录"分支兜底,本计算可以不考虑。
106
+ */
107
+ const isInitial = computed(() => props.list.length > 0 && props.list.every((g) => !g.applied));
108
+ </script>
@@ -0,0 +1,226 @@
1
+ import { computed, reactive } from 'vue';
2
+
3
+ import { datetimeFormatter } from '@tmagic/form';
4
+
5
+ import { useServices } from '@editor/hooks/use-services';
6
+ import type {
7
+ CodeBlockHistoryGroup,
8
+ CodeBlockStepValue,
9
+ DataSourceHistoryGroup,
10
+ DataSourceStepValue,
11
+ HistoryOpType,
12
+ PageHistoryGroup,
13
+ StepValue,
14
+ } from '@editor/type';
15
+
16
+ /**
17
+ * 历史记录面板共享逻辑:
18
+ * - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
19
+ * - 提供折叠状态管理;
20
+ * - 提供操作描述文案生成器。
21
+ *
22
+ * 所有数据基于 historyService 的 reactive state 派生,自动跟随历史变化刷新。
23
+ */
24
+ export const useHistoryList = () => {
25
+ const { historyService } = useServices();
26
+
27
+ /**
28
+ * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
29
+ * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
30
+ */
31
+ const expanded = reactive<Record<string, boolean>>({});
32
+ const toggleGroup = (key: string) => {
33
+ expanded[key] = !expanded[key];
34
+ };
35
+
36
+ const pageGroups = computed(() => historyService.getPageHistoryGroups());
37
+ const dataSourceGroups = computed(() => historyService.getDataSourceHistoryGroups());
38
+ const codeBlockGroups = computed(() => historyService.getCodeBlockHistoryGroups());
39
+
40
+ /** 页面 tab 倒序展示(最新一组在最上面)。 */
41
+ const pageGroupsDisplay = computed(() => pageGroups.value.slice().reverse());
42
+
43
+ /**
44
+ * 把按时间正序的 group 列表,再按 id 聚拢成 bucket(同 id 的所有分组放一起)。
45
+ * 每个 bucket 内部仍然按时间倒序展示(最近的操作最先看到)。
46
+ */
47
+ const groupByTarget = <G extends { id: string | number }>(groups: G[]) => {
48
+ const map = new Map<string | number, G[]>();
49
+ groups.forEach((g) => {
50
+ const list = map.get(g.id) ?? [];
51
+ list.push(g);
52
+ map.set(g.id, list);
53
+ });
54
+ return Array.from(map.entries()).map(([id, gs]) => ({ id, groups: gs.slice().reverse() }));
55
+ };
56
+
57
+ const dataSourceGroupsByTarget = computed(() => groupByTarget(dataSourceGroups.value));
58
+ const codeBlockGroupsByTarget = computed(() => groupByTarget(codeBlockGroups.value));
59
+
60
+ return {
61
+ expanded,
62
+ toggleGroup,
63
+ pageGroups,
64
+ dataSourceGroups,
65
+ codeBlockGroups,
66
+ pageGroupsDisplay,
67
+ dataSourceGroupsByTarget,
68
+ codeBlockGroupsByTarget,
69
+ };
70
+ };
71
+
72
+ /**
73
+ * 历史面板的时间展示:
74
+ * - 当天的记录只显示 `HH:mm:ss`;
75
+ * - 跨天的记录显示 `MM-DD HH:mm:ss`。
76
+ * 无时间戳(旧数据 / 未写入)时返回空串,UI 据此不渲染时间。
77
+ */
78
+ export const formatHistoryTime = (timestamp?: number): string => {
79
+ if (!timestamp) return '';
80
+ const isToday =
81
+ datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD') ===
82
+ (datetimeFormatter(new Date(), '', 'YYYY-MM-DD') as string);
83
+ return `${
84
+ isToday
85
+ ? datetimeFormatter(new Date(timestamp), '', 'HH:mm:ss')
86
+ : datetimeFormatter(new Date(timestamp), '', 'MM-DD HH:mm:ss')
87
+ }`;
88
+ };
89
+
90
+ /** 完整时间(含年份与秒),用于 title 悬浮提示。无时间戳时返回空串。 */
91
+ export const formatHistoryFullTime = (timestamp?: number): string =>
92
+ timestamp ? `${datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD HH:mm:ss')}` : '';
93
+
94
+ /** 取一组历史步骤里最后一步(最近一次)的时间戳,用于组头部展示。 */
95
+ export const groupTimestamp = (group: { steps: { step: { timestamp?: number } }[] }): number | undefined =>
96
+ group.steps[group.steps.length - 1]?.step.timestamp;
97
+
98
+ export const opLabel = (op: HistoryOpType) => {
99
+ switch (op) {
100
+ case 'add':
101
+ return '新增';
102
+ case 'remove':
103
+ return '删除';
104
+ case 'update':
105
+ default:
106
+ return '修改';
107
+ }
108
+ };
109
+
110
+ const nameOf = (node: { name?: string; id?: string | number; type?: string }) =>
111
+ node?.name || node?.type || `${node?.id ?? ''}`;
112
+
113
+ /**
114
+ * 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
115
+ * - 当未传入 id,或 label 本身就是 id 字符串(即没有 name/type/title 可用)时,仅展示 label,避免出现「123 (id: 123)」。
116
+ */
117
+ const labelWithId = (label: string | number | undefined, id: string | number | undefined): string => {
118
+ const labelStr = label === undefined || label === null ? '' : `${label}`;
119
+ if (id === undefined || id === null || id === '') return labelStr;
120
+ if (labelStr === '' || labelStr === `${id}`) return `${id}`;
121
+ return `${labelStr} (id: ${id})`;
122
+ };
123
+
124
+ /** 从一组可选 historyDescription 中取最后一条非空值;都为空时返回 undefined。 */
125
+ const pickLastDescription = (descs: (string | undefined)[]): string | undefined => {
126
+ for (let i = descs.length - 1; i >= 0; i--) {
127
+ if (descs[i]) return descs[i];
128
+ }
129
+ return undefined;
130
+ };
131
+
132
+ export const describePageStep = (step: StepValue) => {
133
+ if (step.historyDescription) return step.historyDescription;
134
+ const { opType } = step;
135
+ if (opType === 'add') {
136
+ const count = step.nodes?.length ?? 0;
137
+ const node = step.nodes?.[0];
138
+ return `新增 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ''}`;
139
+ }
140
+ if (opType === 'remove') {
141
+ const count = step.removedItems?.length ?? 0;
142
+ const node = step.removedItems?.[0]?.node;
143
+ return `删除 ${count} 个节点${count === 1 && node ? `(${labelWithId(nameOf(node), node.id)})` : ''}`;
144
+ }
145
+ const updated = step.updatedItems ?? [];
146
+ if (!updated.length) return '修改节点';
147
+ if (updated.length === 1) {
148
+ const { newNode, changeRecords } = updated[0];
149
+ const propPath = changeRecords?.[0]?.propPath;
150
+ const target = labelWithId(nameOf(newNode), newNode?.id);
151
+ return `修改 ${target}${propPath ? ` · ${propPath}` : ''}`;
152
+ }
153
+ return `修改 ${updated.length} 个节点`;
154
+ };
155
+
156
+ /**
157
+ * 合并组的展示文案:
158
+ * - 若组内任一步显式提供了 historyDescription:取最后一条非空 historyDescription(最近一次的描述更准确);
159
+ * - 单步组:复用 describePageStep;
160
+ * - 多步组(连续修改同一节点):展示节点名 + 涉及的前几个 propPath。
161
+ */
162
+ export const describePageGroup = (group: PageHistoryGroup) => {
163
+ const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
164
+ if (lastDesc) return lastDesc;
165
+ if (group.steps.length === 1) return describePageStep(group.steps[0].step);
166
+ const paths = new Set<string>();
167
+ group.steps.forEach((s) => {
168
+ s.step.updatedItems?.[0]?.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
169
+ });
170
+ const pathList = Array.from(paths).slice(0, 3).join(', ');
171
+ const target = labelWithId(
172
+ group.targetName ?? (group.targetId !== undefined ? `${group.targetId}` : '节点'),
173
+ group.targetId,
174
+ );
175
+ return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
176
+ };
177
+
178
+ export const describeDataSourceStep = (step: DataSourceStepValue) => {
179
+ if (step.historyDescription) return step.historyDescription;
180
+ if (step.oldSchema === null && step.newSchema)
181
+ return `创建 ${labelWithId(step.newSchema.title, step.newSchema.id ?? step.id)}`;
182
+ if (step.newSchema === null && step.oldSchema)
183
+ return `删除 ${labelWithId(step.oldSchema.title, step.oldSchema.id ?? step.id)}`;
184
+ const propPath = step.changeRecords?.[0]?.propPath;
185
+ const title = labelWithId(step.newSchema?.title || step.oldSchema?.title, step.id);
186
+ return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
187
+ };
188
+
189
+ export const describeDataSourceGroup = (group: DataSourceHistoryGroup) => {
190
+ const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
191
+ if (lastDesc) return lastDesc;
192
+ if (group.steps.length === 1) return describeDataSourceStep(group.steps[0].step);
193
+ const paths = new Set<string>();
194
+ group.steps.forEach((s) => {
195
+ s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
196
+ });
197
+ const pathList = Array.from(paths).slice(0, 3).join(', ');
198
+ const rawTitle = group.steps[group.steps.length - 1].step.newSchema?.title || group.steps[0].step.oldSchema?.title;
199
+ const target = labelWithId(rawTitle, group.id);
200
+ return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
201
+ };
202
+
203
+ export const describeCodeBlockStep = (step: CodeBlockStepValue) => {
204
+ if (step.historyDescription) return step.historyDescription;
205
+ if (step.oldContent === null && step.newContent)
206
+ return `创建 ${labelWithId(step.newContent.name, step.newContent.id ?? step.id)}`;
207
+ if (step.newContent === null && step.oldContent)
208
+ return `删除 ${labelWithId(step.oldContent.name, step.oldContent.id ?? step.id)}`;
209
+ const propPath = step.changeRecords?.[0]?.propPath;
210
+ const title = labelWithId(step.newContent?.name || step.oldContent?.name, step.id);
211
+ return propPath ? `修改 ${title} · ${propPath}` : `修改 ${title}`;
212
+ };
213
+
214
+ export const describeCodeBlockGroup = (group: CodeBlockHistoryGroup) => {
215
+ const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
216
+ if (lastDesc) return lastDesc;
217
+ if (group.steps.length === 1) return describeCodeBlockStep(group.steps[0].step);
218
+ const paths = new Set<string>();
219
+ group.steps.forEach((s) => {
220
+ s.step.changeRecords?.forEach((r) => r.propPath && paths.add(r.propPath));
221
+ });
222
+ const pathList = Array.from(paths).slice(0, 3).join(', ');
223
+ const rawName = group.steps[group.steps.length - 1].step.newContent?.name || group.steps[0].step.oldContent?.name;
224
+ const target = labelWithId(rawName, group.id);
225
+ return pathList ? `修改 ${target} · ${pathList}${paths.size > 3 ? '…' : ''}` : `修改 ${target}`;
226
+ };
@@ -9,12 +9,15 @@ import { updateStatus } from '@editor/utils/tree';
9
9
  const createPageNodeStatus = (page: MPage | MPageFragment, initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
10
10
  const map = new Map<Id, LayerNodeStatus>();
11
11
 
12
- map.set(page.id, {
13
- visible: true,
14
- expand: true,
15
- selected: true,
16
- draggable: false,
17
- });
12
+ map.set(
13
+ page.id,
14
+ initialLayerNodeStatus?.get(page.id) || {
15
+ visible: true,
16
+ expand: true,
17
+ selected: true,
18
+ draggable: false,
19
+ },
20
+ );
18
21
 
19
22
  page.items.forEach((node: MNode) =>
20
23
  traverseNode<MNode>(node, (node) => {
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-misused-promises */
2
1
  /*
3
2
  * Tencent is pleased to support the open source community by making TMagicEditor available.
4
3
  *
@@ -19,45 +18,32 @@
19
18
 
20
19
  import { EventEmitter } from 'events';
21
20
 
22
- import { compose } from '@editor/utils/compose';
23
-
24
21
  const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpperCase()}${name.substring(1)}`;
25
22
 
26
23
  const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
27
24
 
28
- const doAction = (
29
- args: any[],
30
- scope: any,
31
- sourceMethod: any,
32
- beforeMethodName: string,
33
- afterMethodName: string,
34
- fn: (args: any[], next?: Function | undefined) => void,
35
- ) => {
36
- try {
37
- let beforeArgs = args;
25
+ const doAction = (args: any[], scope: any, sourceMethod: any, beforeMethodName: string, afterMethodName: string) => {
26
+ let beforeArgs = args;
38
27
 
39
- for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
40
- beforeArgs = beforeMethod(...beforeArgs) || [];
28
+ for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
29
+ beforeArgs = beforeMethod(...beforeArgs) || [];
41
30
 
42
- if (isError(beforeArgs)) throw beforeArgs;
31
+ if (isError(beforeArgs)) throw beforeArgs;
43
32
 
44
- if (!Array.isArray(beforeArgs)) {
45
- beforeArgs = [beforeArgs];
46
- }
33
+ if (!Array.isArray(beforeArgs)) {
34
+ beforeArgs = [beforeArgs];
47
35
  }
36
+ }
48
37
 
49
- let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope));
50
-
51
- for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
52
- returnValue = afterMethod(returnValue, ...beforeArgs);
38
+ let returnValue: any = sourceMethod.apply(scope, beforeArgs);
53
39
 
54
- if (isError(returnValue)) throw returnValue;
55
- }
40
+ for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
41
+ returnValue = afterMethod(returnValue, ...beforeArgs);
56
42
 
57
- return returnValue;
58
- } catch (error) {
59
- throw error;
43
+ if (isError(returnValue)) throw returnValue;
60
44
  }
45
+
46
+ return returnValue;
61
47
  };
62
48
 
63
49
  const doAsyncAction = async (
@@ -66,40 +52,34 @@ const doAsyncAction = async (
66
52
  sourceMethod: any,
67
53
  beforeMethodName: string,
68
54
  afterMethodName: string,
69
- fn: (args: any[], next?: Function | undefined) => Promise<void> | void,
70
55
  ) => {
71
- try {
72
- let beforeArgs = args;
56
+ let beforeArgs = args;
73
57
 
74
- for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
75
- beforeArgs = (await beforeMethod(...beforeArgs)) || [];
58
+ for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
59
+ beforeArgs = (await beforeMethod(...beforeArgs)) || [];
76
60
 
77
- if (isError(beforeArgs)) throw beforeArgs;
61
+ if (isError(beforeArgs)) throw beforeArgs;
78
62
 
79
- if (!Array.isArray(beforeArgs)) {
80
- beforeArgs = [beforeArgs];
81
- }
63
+ if (!Array.isArray(beforeArgs)) {
64
+ beforeArgs = [beforeArgs];
82
65
  }
66
+ }
83
67
 
84
- let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope));
85
-
86
- for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
87
- returnValue = await afterMethod(returnValue, ...beforeArgs);
68
+ let returnValue: any = await sourceMethod.apply(scope, beforeArgs);
88
69
 
89
- if (isError(returnValue)) throw returnValue;
90
- }
70
+ for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
71
+ returnValue = await afterMethod(returnValue, ...beforeArgs);
91
72
 
92
- return returnValue;
93
- } catch (error) {
94
- throw error;
73
+ if (isError(returnValue)) throw returnValue;
95
74
  }
75
+
76
+ return returnValue;
96
77
  };
97
78
 
98
79
  /**
99
- * 提供两种方式对Class进行扩展
100
- * 方法1:
80
+ * Class进行扩展
101
81
  * 给Class中的每个方法都添加before after两个钩子
102
- * 给Class添加一个usePlugin方法,use方法可以传入一个包含before或者after方法的对象
82
+ * 给Class添加一个usePlugin方法,usePlugin方法可以传入一个包含before或者after方法的对象
103
83
  *
104
84
  * 例如:
105
85
  * Class EditorService extends BaseService {
@@ -124,27 +104,9 @@ const doAsyncAction = async (
124
104
  *
125
105
  * 调用时的参数会透传到before方法的参数中, 然后before的return 会作为原方法的参数和after的参数,after第一个参数则是原方法的return值;
126
106
  * 如需终止后续方法调用可以return new Error();
127
- *
128
- * 方法2:
129
- * 给Class中的每个方法都添加中间件
130
- * 给Class添加一个use方法,use方法可以传入一个包含源对象方法名作为key值的对象
131
- *
132
- * 例如:
133
- * Class EditorService extends BaseService {
134
- * constructor() {
135
- * super([ { name: 'add', isAsync: true },]);
136
- * }
137
- * add(value) { return result; }
138
- * };
139
- *
140
- * const editorService = new EditorService();
141
- * editorService.use({
142
- * add(value, next) { console.log(value); next() },
143
- * });
144
107
  */
145
- export default class extends EventEmitter {
108
+ class BaseService extends EventEmitter {
146
109
  private pluginOptionsList: Record<string, Function[]> = {};
147
- private middleware: Record<string, Function[]> = {};
148
110
  private taskList: (() => Promise<void>)[] = [];
149
111
  private doingTask = false;
150
112
 
@@ -161,14 +123,12 @@ export default class extends EventEmitter {
161
123
 
162
124
  this.pluginOptionsList[beforeMethodName] = [];
163
125
  this.pluginOptionsList[afterMethodName] = [];
164
- this.middleware[propertyName] = [];
165
126
 
166
- const fn = compose(this.middleware[propertyName], isAsync);
167
127
  Object.defineProperty(scope, propertyName, {
168
128
  value: isAsync
169
129
  ? async (...args: any[]) => {
170
130
  if (!serialMethods.includes(propertyName)) {
171
- return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
131
+ return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName);
172
132
  }
173
133
 
174
134
  // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
@@ -176,7 +136,7 @@ export default class extends EventEmitter {
176
136
  const promise = new Promise<any>((resolve, reject) => {
177
137
  this.taskList.push(async () => {
178
138
  try {
179
- const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
139
+ const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName);
180
140
  resolve(value);
181
141
  } catch (e) {
182
142
  reject(e);
@@ -190,20 +150,11 @@ export default class extends EventEmitter {
190
150
 
191
151
  return promise;
192
152
  }
193
- : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn),
153
+ : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName),
194
154
  });
195
155
  });
196
156
  }
197
157
 
198
- /**
199
- * @deprecated 请使用usePlugin代替
200
- */
201
- public use(options: Record<string, Function>) {
202
- for (const [methodName, method] of Object.entries(options)) {
203
- if (typeof method === 'function') this.middleware[methodName].push(method);
204
- }
205
- }
206
-
207
158
  public usePlugin(options: Record<string, Function>) {
208
159
  for (const [methodName, method] of Object.entries(options)) {
209
160
  if (typeof method === 'function' && !this.pluginOptionsList[methodName].includes(method)) {
@@ -224,10 +175,6 @@ export default class extends EventEmitter {
224
175
  for (const key of Object.keys(this.pluginOptionsList)) {
225
176
  this.pluginOptionsList[key] = [];
226
177
  }
227
-
228
- for (const key of Object.keys(this.middleware)) {
229
- this.middleware[key] = [];
230
- }
231
178
  }
232
179
 
233
180
  private async doTask() {
@@ -240,3 +187,5 @@ export default class extends EventEmitter {
240
187
  this.doingTask = false;
241
188
  }
242
189
  }
190
+
191
+ export default BaseService;