@tmagic/editor 1.8.0-beta.3 → 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.
Files changed (92) hide show
  1. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +15 -1
  2. package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
  3. package/dist/es/hooks/use-code-block-edit.js +6 -3
  4. package/dist/es/hooks/use-data-source-edit.js +5 -2
  5. package/dist/es/hooks/use-stage.js +6 -3
  6. package/dist/es/index.js +6 -3
  7. package/dist/es/initService.js +1 -1
  8. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -51
  11. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -36
  12. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +152 -93
  13. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +36 -10
  14. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +334 -208
  15. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
  16. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -57
  17. package/dist/es/layouts/history-list/composables.js +118 -25
  18. package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
  19. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +5 -1
  20. package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
  22. package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +3 -3
  23. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
  25. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  26. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
  27. package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +5 -5
  28. package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
  29. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +1 -1
  30. package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +8 -8
  31. package/dist/es/services/codeBlock.js +105 -42
  32. package/dist/es/services/dataSource.js +85 -35
  33. package/dist/es/services/editor.js +317 -122
  34. package/dist/es/services/history.js +270 -204
  35. package/dist/es/services/keybinding.js +3 -3
  36. package/dist/es/services/ui.js +3 -0
  37. package/dist/es/style.css +65 -10
  38. package/dist/es/utils/content-menu.js +17 -9
  39. package/dist/es/utils/editor.js +35 -1
  40. package/dist/es/utils/history.js +223 -0
  41. package/dist/es/utils/indexed-db.js +86 -0
  42. package/dist/es/utils/undo-redo.js +60 -1
  43. package/dist/style.css +65 -10
  44. package/dist/tmagic-editor.umd.cjs +2047 -954
  45. package/package.json +7 -7
  46. package/src/components/CompareForm.vue +21 -1
  47. package/src/components/ToolButton.vue +2 -2
  48. package/src/hooks/use-code-block-edit.ts +4 -3
  49. package/src/hooks/use-data-source-edit.ts +2 -2
  50. package/src/hooks/use-stage.ts +3 -3
  51. package/src/index.ts +1 -0
  52. package/src/initService.ts +1 -1
  53. package/src/layouts/Framework.vue +1 -1
  54. package/src/layouts/NavMenu.vue +1 -1
  55. package/src/layouts/history-list/Bucket.vue +34 -67
  56. package/src/layouts/history-list/BucketTab.vue +46 -51
  57. package/src/layouts/history-list/GroupRow.vue +150 -98
  58. package/src/layouts/history-list/HistoryDiffDialog.vue +96 -69
  59. package/src/layouts/history-list/HistoryListPanel.vue +298 -111
  60. package/src/layouts/history-list/InitialRow.vue +25 -9
  61. package/src/layouts/history-list/PageTab.vue +58 -48
  62. package/src/layouts/history-list/composables.ts +210 -29
  63. package/src/layouts/page-bar/PageBar.vue +4 -2
  64. package/src/layouts/props-panel/PropsPanel.vue +5 -1
  65. package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
  66. package/src/layouts/sidebar/Sidebar.vue +6 -1
  67. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
  68. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
  69. package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
  70. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  71. package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
  72. package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
  73. package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
  74. package/src/layouts/workspace/viewer/Stage.vue +1 -1
  75. package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
  76. package/src/services/codeBlock.ts +129 -35
  77. package/src/services/dataSource.ts +105 -34
  78. package/src/services/editor.ts +470 -157
  79. package/src/services/history.ts +312 -203
  80. package/src/services/keybinding.ts +3 -3
  81. package/src/services/ui.ts +7 -0
  82. package/src/theme/history-list-panel.scss +87 -6
  83. package/src/theme/page-bar.scss +0 -4
  84. package/src/theme/props-panel.scss +3 -3
  85. package/src/type.ts +221 -61
  86. package/src/utils/content-menu.ts +18 -9
  87. package/src/utils/editor.ts +41 -1
  88. package/src/utils/history.ts +298 -0
  89. package/src/utils/index.ts +2 -0
  90. package/src/utils/indexed-db.ts +122 -0
  91. package/src/utils/undo-redo.ts +88 -0
  92. package/types/index.d.ts +871 -308
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.3",
2
+ "version": "1.8.0-beta.5",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,11 +58,11 @@
58
58
  "moveable": "^0.53.0",
59
59
  "serialize-javascript": "^7.0.0",
60
60
  "sortablejs": "^1.15.6",
61
- "@tmagic/design": "1.8.0-beta.3",
62
- "@tmagic/form": "1.8.0-beta.3",
63
- "@tmagic/stage": "1.8.0-beta.3",
64
- "@tmagic/utils": "1.8.0-beta.3",
65
- "@tmagic/table": "1.8.0-beta.3"
61
+ "@tmagic/form": "1.8.0-beta.5",
62
+ "@tmagic/table": "1.8.0-beta.5",
63
+ "@tmagic/utils": "1.8.0-beta.5",
64
+ "@tmagic/design": "1.8.0-beta.5",
65
+ "@tmagic/stage": "1.8.0-beta.5"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -76,7 +76,7 @@
76
76
  "type-fest": "^5.2.0",
77
77
  "typescript": "^6.0.3",
78
78
  "vue": "^3.5.34",
79
- "@tmagic/core": "1.8.0-beta.3"
79
+ "@tmagic/core": "1.8.0-beta.5"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
@@ -152,6 +152,24 @@ const showDiff = ({ curValue, lastValue, config }: { curValue: any; lastValue: a
152
152
  return !isEqual(curValue, lastValue);
153
153
  };
154
154
 
155
+ const removeStyleDisplayConfig = (formConfig: FormConfig): FormConfig =>
156
+ formConfig.map((item) => {
157
+ if (!('type' in item)) return item;
158
+ if (item?.type !== 'tab' || !Array.isArray(item.items)) return item;
159
+
160
+ return {
161
+ ...item,
162
+ items: item.items.map((tabPane) => {
163
+ if (tabPane?.title !== '样式' || !Array.isArray(tabPane.items)) return tabPane;
164
+
165
+ return {
166
+ ...tabPane,
167
+ display: true,
168
+ };
169
+ }),
170
+ };
171
+ });
172
+
155
173
  /**
156
174
  * 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
157
175
  * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
@@ -162,7 +180,9 @@ const defaultLoadConfig = async (): Promise<FormConfig> => {
162
180
  if (!props.type) {
163
181
  return [];
164
182
  }
165
- return await propsService.getPropsConfig(props.type);
183
+ return removeStyleDisplayConfig(
184
+ await propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }),
185
+ );
166
186
  }
167
187
  case 'data-source': {
168
188
  return dataSourceService.getFormConfig(props.type || 'base');
@@ -13,7 +13,7 @@
13
13
 
14
14
  <template v-else-if="data.type === 'button'">
15
15
  <TMagicTooltip v-if="data.tooltip" effect="dark" placement="bottom-start" :content="data.tooltip">
16
- <TMagicButton size="small" link :disabled="disabled">
16
+ <TMagicButton size="small" link :disabled="disabled" v-bind="data.buttonProps || {}">
17
17
  <template #icon v-if="data.icon">
18
18
  <MIcon :icon="data.icon"></MIcon>
19
19
  </template>
@@ -21,7 +21,7 @@
21
21
  </TMagicButton>
22
22
  </TMagicTooltip>
23
23
 
24
- <TMagicButton v-else size="small" link :disabled="disabled" :title="data.text">
24
+ <TMagicButton v-else size="small" link :disabled="disabled" :title="data.text" v-bind="data.buttonProps || {}">
25
25
  <template #icon v-if="data.icon">
26
26
  <MIcon :icon="data.icon"></MIcon>
27
27
  </template>
@@ -6,7 +6,7 @@ import { tMagicMessage } from '@tmagic/design';
6
6
  import type { ContainerChangeEventData } from '@tmagic/form';
7
7
 
8
8
  import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
9
- import type { Services } from '@editor/type';
9
+ import type { HistoryOpSource, Services } from '@editor/type';
10
10
 
11
11
  export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService']) => {
12
12
  const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
@@ -58,8 +58,8 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
58
58
  };
59
59
 
60
60
  // 删除代码块
61
- const deleteCode = async (key: string) => {
62
- codeBlockService.deleteCodeDslByIds([key]);
61
+ const deleteCode = async (key: string, { historySource }: { historySource?: HistoryOpSource } = {}) => {
62
+ codeBlockService.deleteCodeDslByIds([key], { historySource });
63
63
  };
64
64
 
65
65
  const submitCodeBlockHandler = async (values: CodeBlockContent, eventData?: ContainerChangeEventData) => {
@@ -67,6 +67,7 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
67
67
 
68
68
  await codeBlockService.setCodeDslById(codeId.value, values, {
69
69
  changeRecords: eventData?.changeRecords,
70
+ historySource: 'props',
70
71
  });
71
72
 
72
73
  codeBlockEditorRef.value?.hide();
@@ -27,9 +27,9 @@ export const useDataSourceEdit = (dataSourceService: Services['dataSourceService
27
27
 
28
28
  const submitDataSourceHandler = (value: DataSourceSchema, eventData: ContainerChangeEventData) => {
29
29
  if (value.id) {
30
- dataSourceService.update(value, { changeRecords: eventData.changeRecords });
30
+ dataSourceService.update(value, { changeRecords: eventData.changeRecords, historySource: 'props' });
31
31
  } else {
32
- dataSourceService.add(value);
32
+ dataSourceService.add(value, { historySource: 'props' });
33
33
  }
34
34
 
35
35
  editDialog.value?.hide();
@@ -130,16 +130,16 @@ export const useStage = (stageOptions: StageOptions) => {
130
130
  });
131
131
  if (configs.length === 0) return;
132
132
 
133
- editorService.update(configs, { changeRecordList });
133
+ editorService.update(configs, { changeRecordList, historySource: 'stage' });
134
134
  });
135
135
 
136
136
  stage.on('sort', (ev: SortEventData) => {
137
- editorService.sort(ev.src, ev.dist);
137
+ editorService.sort(ev.src, ev.dist, { historySource: 'stage' });
138
138
  });
139
139
 
140
140
  stage.on('remove', (ev: RemoveEventData) => {
141
141
  const nodes = ev.data.map(({ el }) => editorService.getNodeById(getIdFromEl()(el) || ''));
142
- editorService.remove(nodes.filter((node) => Boolean(node)) as MNode[]);
142
+ editorService.remove(nodes.filter((node) => Boolean(node)) as MNode[], { historySource: 'stage' });
143
143
  });
144
144
 
145
145
  stage.on('select-parent', () => {
package/src/index.ts CHANGED
@@ -72,6 +72,7 @@ export { default as Resizer } from './components/Resizer.vue';
72
72
  export { default as CodeBlockEditor } from './components/CodeBlockEditor.vue';
73
73
  export { default as CompareForm } from './components/CompareForm.vue';
74
74
  export { default as HistoryListBucket } from './layouts/history-list/Bucket.vue';
75
+ export { default as HistoryListBucketTab } from './layouts/history-list/BucketTab.vue';
75
76
  export { default as HistoryDiffDialog } from './layouts/history-list/HistoryDiffDialog.vue';
76
77
  export { default as FloatingBox } from './components/FloatingBox.vue';
77
78
  export { default as Tree } from './components/Tree.vue';
@@ -55,7 +55,7 @@ export const initServiceState = (
55
55
  watch(
56
56
  () => props.modelValue,
57
57
  (modelValue) => {
58
- editorService.set('root', modelValue || null);
58
+ editorService.set('root', modelValue || null, { historySource: 'initial' });
59
59
  },
60
60
  {
61
61
  immediate: true,
@@ -168,7 +168,7 @@ onBeforeUnmount(() => {
168
168
  const saveCode = (value: string) => {
169
169
  try {
170
170
  const parseDSL = getEditorConfig('parseDSL');
171
- editorService.set('root', parseDSL(value));
171
+ editorService.set('root', parseDSL(value), { historySource: 'root-code' });
172
172
  } catch (e: any) {
173
173
  console.error(e);
174
174
  }
@@ -80,7 +80,7 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => {
80
80
  disabled: () => editorService.get('node')?.type === NodeType.PAGE,
81
81
  handler: () => {
82
82
  const node = editorService.get('node');
83
- node && editorService.remove(node);
83
+ node && editorService.remove(node, { historySource: 'toolbar' });
84
84
  },
85
85
  });
86
86
  break;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="m-editor-history-list-bucket">
3
3
  <div class="m-editor-history-list-bucket-title">
4
- <span>{{ title }}</span>
4
+ <span>{{ config.title }}</span>
5
5
  <code>{{ String(bucketId) }}</code>
6
6
  <span class="m-editor-history-list-bucket-count">{{ groups.length }} 组</span>
7
7
  </div>
@@ -9,30 +9,10 @@
9
9
  <ul class="m-editor-history-list-ul">
10
10
  <GroupRow
11
11
  v-for="group in groups"
12
- :key="`${prefix}-${bucketId}-${group.steps[0]?.index}`"
13
- :group-key="`${prefix}-${bucketId}-${group.steps[0]?.index}`"
14
- :applied="group.applied"
15
- :merged="group.steps.length > 1"
16
- :op-type="group.opType"
17
- :desc="describeGroup(group)"
18
- :time="formatHistoryTime(groupTimestamp(group))"
19
- :time-title="formatHistoryFullTime(groupTimestamp(group))"
20
- :step-count="group.steps.length"
21
- :sub-steps="
22
- group.steps.map((s: any) => ({
23
- index: s.index,
24
- applied: s.applied,
25
- isCurrent: s.isCurrent,
26
- desc: describeStep(s.step),
27
- diffable: isStepDiffable ? isStepDiffable(s.step) : false,
28
- revertable: s.applied,
29
- time: formatHistoryTime(s.step.timestamp),
30
- timeTitle: formatHistoryFullTime(s.step.timestamp),
31
- }))
32
- "
33
- :is-current="group.isCurrent"
34
- :expanded="!!expanded[`${prefix}-${bucketId}-${group.steps[0]?.index}`]"
35
- :goto-enabled="gotoEnabled"
12
+ :key="rowKey(group)"
13
+ :group="toRow(group)"
14
+ :expanded="isHistoryGroupExpanded(expanded, rowKey(group))"
15
+ :goto-enabled="config.gotoEnabled"
36
16
  @toggle="(key: string) => $emit('toggle', key)"
37
17
  @goto="(index: number) => $emit('goto', bucketId, index)"
38
18
  @diff-step="(index: number) => $emit('diff-step', bucketId, index)"
@@ -41,24 +21,25 @@
41
21
  <!--
42
22
  初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。
43
23
  当 bucket 内所有 group 都未 applied 时即为当前位置。
44
- showInitial=false 时不展示(用于没有"撤销到初始状态"语义的自定义历史,如业务模块历史)。
24
+ config.showInitial=false 时不展示(用于没有"撤销到初始状态"语义的自定义历史,如业务模块历史)。
45
25
  -->
46
26
  <InitialRow
47
- v-if="showInitial !== false"
27
+ v-if="config.showInitial !== false"
48
28
  :is-current="isInitial"
49
- :goto-enabled="gotoEnabled"
29
+ :goto-enabled="config.gotoEnabled"
50
30
  @goto-initial="$emit('goto-initial', bucketId)"
51
31
  />
52
32
  </ul>
53
33
  </div>
54
34
  </template>
55
35
 
56
- <script lang="ts" setup>
36
+ <script lang="ts" setup generic="T extends BaseStepValue = BaseStepValue">
57
37
  import { computed } from 'vue';
58
38
 
59
- import type { HistoryOpType } from '@editor/type';
39
+ import type { BaseStepValue, HistoryBucketConfig } from '@editor/type';
60
40
 
61
- import { formatHistoryFullTime, formatHistoryTime, groupTimestamp } from './composables';
41
+ import type { HistoryBucketGroup, HistoryRowGroup } from './composables';
42
+ import { isHistoryGroupExpanded, toRowGroup } from './composables';
62
43
  import GroupRow from './GroupRow.vue';
63
44
  import InitialRow from './InitialRow.vue';
64
45
 
@@ -66,42 +47,19 @@ defineOptions({
66
47
  name: 'MEditorHistoryListBucket',
67
48
  });
68
49
 
69
- const props = withDefaults(
70
- defineProps<{
71
- /** Bucket 标题,例如 "数据源" / "代码块",渲染在 bucket 头部。 */
72
- title: string;
73
- /** 当前 bucket 对应的目标 id(dataSource.id 或 codeBlock.id),同时用于组装子项的 key。 */
74
- bucketId: string | number;
75
- /**
76
- * 子项 key 的命名空间前缀:内置 `ds` 表示数据源,`cb` 表示代码块;
77
- * 业务方复用 Bucket 时可传入自定义前缀(如 `mod`)。与上层折叠状态 key 保持一致。
78
- */
79
- prefix: string;
80
- /** 是否展示底部「回到初始状态」入口,默认 true。无 undo cursor 语义的自定义历史可传 false 关闭。 */
81
- showInitial?: boolean;
82
- /** 当前 bucket 下的所有历史分组,按时间倒序展示(最近的操作在前)。 */
83
- groups: {
84
- applied: boolean;
85
- isCurrent?: boolean;
86
- opType: HistoryOpType;
87
- steps: { index: number; applied: boolean; isCurrent?: boolean; step: any }[];
88
- }[];
89
- /** 组级描述文案生成器,接收一个 group,返回展示文本。由父组件按业务类型注入。 */
90
- describeGroup: (_group: any) => string;
91
- /** 单步描述文案生成器,接收一个 step,返回展示文本。用于合并组展开后的子步列表。 */
92
- describeStep: (_step: any) => string;
93
- /** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入;不传则一律不展示差异入口。 */
94
- isStepDiffable?: (_step: any) => boolean;
95
- /** 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护以便跨 tab 复用。 */
96
- expanded: Record<string, boolean>;
97
- /** 是否支持「跳转到该记录」(goto)。默认 true。 */
98
- gotoEnabled?: boolean;
99
- }>(),
100
- {
101
- showInitial: true,
102
- gotoEnabled: true,
103
- },
104
- );
50
+ const props = defineProps<{
51
+ /**
52
+ * 该类历史的整体渲染配置(title / prefix / describe* / isStep* / showInitial / gotoEnabled)。
53
+ * 由父组件按业务类型注入,组件内部按需读取,避免逐项透传多个 props。
54
+ */
55
+ config: HistoryBucketConfig<T>;
56
+ /** 当前 bucket 对应的目标 id(dataSource.id 或 codeBlock.id),同时用于组装子项的 key。 */
57
+ bucketId: string | number;
58
+ /** 当前 bucket 下的所有历史分组,按时间倒序展示(最近的操作在前)。 */
59
+ groups: HistoryBucketGroup<T>[];
60
+ /** 共享的折叠状态表(key -> 是否展开,缺省或 true 为展开、false 为收起),由顶层 panel 统一维护以便跨 tab 复用。 */
61
+ expanded: Record<string, boolean>;
62
+ }>();
105
63
 
106
64
  defineEmits<{
107
65
  /** 透传子组件 GroupRow 的 toggle,由上层 panel 更新 expanded。 */
@@ -119,6 +77,15 @@ defineEmits<{
119
77
  (_e: 'revert-step', _bucketId: string | number, _index: number): void;
120
78
  }>();
121
79
 
80
+ /**
81
+ * 子项 / 折叠状态 key:`${prefix}-${bucketId}-${组内首步 index}`。
82
+ * 以稳定的 step 索引(而非展示位置)标识分组,历史数据更新后已展开的分组状态仍能正确保持。
83
+ */
84
+ const rowKey = (group: HistoryBucketGroup<T>) => `${props.config.prefix}-${props.bucketId}-${group.steps[0]?.index}`;
85
+
86
+ /** 把原始分组派生为 GroupRow 直接消费的视图模型。 */
87
+ const toRow = (group: HistoryBucketGroup<T>): HistoryRowGroup => toRowGroup(group, rowKey(group), props.config);
88
+
122
89
  /** 该 bucket 是否处于初始状态(栈 cursor=0),等价于全部 group 都未 applied。 */
123
90
  const isInitial = computed(() => props.groups.length > 0 && props.groups.every((g) => !g.applied));
124
91
  </script>
@@ -1,66 +1,59 @@
1
1
  <template>
2
2
  <div v-if="!buckets.length" class="m-editor-history-list-empty">暂无操作记录</div>
3
- <TMagicScrollbar v-else max-height="360px">
4
- <Bucket
5
- v-for="bucket in buckets"
6
- :key="`${prefix}-${bucket.id}`"
7
- :title="title"
8
- :bucket-id="bucket.id"
9
- :prefix="prefix"
10
- :groups="bucket.groups"
11
- :describe-group="describeGroup"
12
- :describe-step="describeStep"
13
- :is-step-diffable="isStepDiffable"
14
- :expanded="expanded"
15
- :goto-enabled="gotoEnabled"
16
- @toggle="(key: string) => $emit('toggle', key)"
17
- @goto="(id: string | number, index: number) => $emit('goto', id, index)"
18
- @goto-initial="(id: string | number) => $emit('goto-initial', id)"
19
- @diff-step="(id: string | number, index: number) => $emit('diff-step', id, index)"
20
- @revert-step="(id: string | number, index: number) => $emit('revert-step', id, index)"
21
- />
22
- </TMagicScrollbar>
3
+ <template v-else>
4
+ <div class="m-editor-history-list-toolbar">
5
+ <span class="m-editor-history-list-clear" :title="`清空${config.title}的历史记录`" @click="$emit('clear')"
6
+ >清空</span
7
+ >
8
+ </div>
9
+ <TMagicScrollbar max-height="360px">
10
+ <Bucket
11
+ v-for="bucket in buckets"
12
+ :key="`${config.prefix}-${bucket.id}`"
13
+ :config="config"
14
+ :bucket-id="bucket.id"
15
+ :groups="bucket.groups"
16
+ :expanded="expanded"
17
+ @toggle="(key: string) => $emit('toggle', key)"
18
+ @goto="(id: string | number, index: number) => $emit('goto', id, index)"
19
+ @goto-initial="(id: string | number) => $emit('goto-initial', id)"
20
+ @diff-step="(id: string | number, index: number) => $emit('diff-step', id, index)"
21
+ @revert-step="(id: string | number, index: number) => $emit('revert-step', id, index)"
22
+ />
23
+ </TMagicScrollbar>
24
+ </template>
23
25
  </template>
24
26
 
25
- <script lang="ts" setup>
27
+ <script lang="ts" setup generic="T extends BaseStepValue = BaseStepValue">
26
28
  import { TMagicScrollbar } from '@tmagic/design';
27
29
 
30
+ import type { BaseStepValue, HistoryBucketConfig } from '@editor/type';
31
+
28
32
  import Bucket from './Bucket.vue';
33
+ import type { HistoryBucketGroup } from './composables';
29
34
 
30
35
  defineOptions({
31
36
  name: 'MEditorHistoryListBucketTab',
32
37
  });
33
38
 
34
- withDefaults(
35
- defineProps<{
36
- /** bucket 头部展示的标题,例如 "数据源" / "代码块"。 */
37
- title: string;
38
- /** 子项 key 的命名空间前缀(`ds` 数据源 / `cb` 代码块),与上层折叠状态 key 保持一致。 */
39
- prefix: string;
40
- /**
41
- * 已按目标 id 聚拢成的 bucket 列表,每个 bucket 内部的 groups 已按时间倒序排好。
42
- * 空数组时显示空态。
43
- */
44
- buckets: { id: string | number; groups: any[] }[];
45
- /** 组级描述文案生成器,由父组件按业务类型注入。 */
46
- describeGroup: (_group: any) => string;
47
- /** 单步描述文案生成器,由父组件按业务类型注入。 */
48
- describeStep: (_step: any) => string;
49
- /** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入。 */
50
- isStepDiffable: (_step: any) => boolean;
51
- /**
52
- * 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。
53
- * 本 tab 使用 `${prefix}-${id}-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
54
- * 这样历史数据更新后已展开的分组状态仍能正确保持。
55
- */
56
- expanded: Record<string, boolean>;
57
- /** 是否支持「跳转到该记录」(goto),透传给 Bucket。默认 true。 */
58
- gotoEnabled?: boolean;
59
- }>(),
60
- {
61
- gotoEnabled: true,
62
- },
63
- );
39
+ defineProps<{
40
+ /**
41
+ * 该类历史的整体渲染配置(title / prefix / describe* / isStep* / showInitial / gotoEnabled),
42
+ * 由父组件按业务类型注入并整体透传给 Bucket,避免逐项透传多个 props。
43
+ */
44
+ config: HistoryBucketConfig<T>;
45
+ /**
46
+ * 已按目标 id 聚拢成的 bucket 列表,每个 bucket 内部的 groups 已按时间倒序排好。
47
+ * 空数组时显示空态。
48
+ */
49
+ buckets: { id: string | number; groups: HistoryBucketGroup<T>[] }[];
50
+ /**
51
+ * 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。
52
+ * key 形如 `${prefix}-${id}-${组内首步 index}`——以稳定的 step 索引而非展示位置标识分组,
53
+ * 这样历史数据更新后已展开的分组状态仍能正确保持。
54
+ */
55
+ expanded: Record<string, boolean>;
56
+ }>();
64
57
 
65
58
  defineEmits<{
66
59
  /** 透传子组件 Bucket 的 toggle 事件给上层 panel,由其更新 expanded。 */
@@ -73,5 +66,7 @@ defineEmits<{
73
66
  (_e: 'diff-step', _targetId: string | number, _index: number): void;
74
67
  /** 透传 Bucket 的 revert-step 事件,携带目标 id 与 step 索引(类 git revert)。 */
75
68
  (_e: 'revert-step', _targetId: string | number, _index: number): void;
69
+ /** 用户点击"清空"按钮,请求清空该类(数据源 / 代码块)的全部历史记录(由上层弹窗二次确认后执行)。 */
70
+ (_e: 'clear'): void;
76
71
  }>();
77
72
  </script>