@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
package/src/type.ts CHANGED
@@ -22,7 +22,17 @@ import type * as Monaco from 'monaco-editor';
22
22
  import type { default as Sortable, Options, SortableEvent } from 'sortablejs';
23
23
  import type { PascalCasedProperties, Writable } from 'type-fest';
24
24
 
25
- import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/core';
25
+ import type {
26
+ CodeBlockContent,
27
+ CodeBlockDSL,
28
+ DataSourceSchema,
29
+ Id,
30
+ MApp,
31
+ MContainer,
32
+ MNode,
33
+ MPage,
34
+ MPageFragment,
35
+ } from '@tmagic/core';
26
36
  import type { ChangeRecord, FormConfig, TableColumnConfig, TypeFunction } from '@tmagic/form';
27
37
  import type StageCore from '@tmagic/stage';
28
38
  import type {
@@ -138,7 +148,7 @@ export interface EditorInstallOptions {
138
148
  customCreateMonacoDiffEditor: (
139
149
  monaco: typeof import('monaco-editor'),
140
150
  codeEditorEl: HTMLElement,
141
- options: Monaco.editor.IStandaloneEditorConstructionOptions & { editorCustomType?: string },
151
+ options: Monaco.editor.IStandaloneDiffEditorConstructionOptions & { editorCustomType?: string },
142
152
  ) => Promise<Monaco.editor.IStandaloneDiffEditor> | Monaco.editor.IStandaloneDiffEditor;
143
153
  [key: string]: any;
144
154
  }
@@ -189,6 +199,11 @@ export interface StageOptions {
189
199
  */
190
200
  alwaysMultiSelect?: boolean;
191
201
  disabledRule?: boolean;
202
+ /**
203
+ * 禁用「非点击画布选中组件时(如从图层树、面包屑等外部选中),对选中区域做高亮闪烁提示」,
204
+ * 默认 false(即默认开启闪烁)
205
+ */
206
+ disabledFlashTip?: boolean;
192
207
  zoom?: number;
193
208
  /** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
194
209
  beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
@@ -407,6 +422,7 @@ export interface MenuComponent {
407
422
  * 'rule': 显示隐藏标尺
408
423
  * 'scale-to-original': 缩放到实际大小
409
424
  * 'scale-to-fit': 缩放以适应
425
+ * 'history-list': 历史记录面板(按 页面 / 数据源 / 代码块 三个 tab 展示,相邻同目标修改自动合并)
410
426
  */
411
427
  // #region MenuItem
412
428
  export type MenuItem =
@@ -421,6 +437,7 @@ export type MenuItem =
421
437
  | 'rule'
422
438
  | 'scale-to-original'
423
439
  | 'scale-to-fit'
440
+ | 'history-list'
424
441
  | MenuButton
425
442
  | MenuComponent
426
443
  | string;
@@ -463,6 +480,63 @@ export interface SideComponent extends MenuComponent {
463
480
  }
464
481
  // #endregion SideComponent
465
482
 
483
+ // #region HistoryListExtraTab
484
+ /**
485
+ * 历史记录面板(HistoryListPanel)的自定义扩展 tab。
486
+ *
487
+ * 业务方可通过 Editor 的 `historyListExtraTabs` 注入额外的历史记录 tab,
488
+ * 例如某个自定义模块维护自己的操作历史时,可以在历史记录面板中增加一个
489
+ * 独立的 tab 来展示与回滚。内置的「页面 / 数据源 / 代码块」三个 tab 之后
490
+ * 会依次追加这些扩展 tab。
491
+ */
492
+ export interface HistoryListExtraTab {
493
+ /** tab 唯一标识,作为 TMagicTabs 的 name */
494
+ name: string;
495
+ /** tab 显示文案,支持传入函数以展示动态内容(如记录数量) */
496
+ label: string | (() => string);
497
+ /** tab 内容区渲染的组件(Vue 组件或字符串标签) */
498
+ component: any;
499
+ /** 传入内容组件的 props */
500
+ props?: Record<string, any>;
501
+ /** 内容组件的事件监听 */
502
+ listeners?: Record<string, (..._args: any[]) => any>;
503
+ }
504
+ // #endregion HistoryListExtraTab
505
+
506
+ // #region CompareForm
507
+ /**
508
+ * 对比表单(CompareForm)的对比类型:
509
+ * - node: 节点组件,按 `type` 从 propsService 获取属性表单配置
510
+ * - data-source: 数据源,按 `type`(base/http/...) 从 dataSourceService 获取数据源表单配置
511
+ * - code-block: 数据源代码块,使用内置的代码块表单配置
512
+ */
513
+ export type CompareCategory = 'node' | 'data-source' | 'code-block';
514
+
515
+ /**
516
+ * 自定义 `loadConfig` 时回传的上下文,聚合了组件当前的对比入参,
517
+ * 方便调用方在外部按需拼装 FormConfig。
518
+ */
519
+ export interface CompareFormLoadConfigContext {
520
+ /** 对比类型,见 CompareCategory */
521
+ category: string;
522
+ /** 节点 / 数据源类型 */
523
+ type?: string;
524
+ /** 数据源代码块场景下的数据源类型 */
525
+ dataSourceType?: string;
526
+ /**
527
+ * 内置的默认 FormConfig 加载逻辑(按 `category` 从 propsService / dataSourceService /
528
+ * 代码块工具取配置)。自定义 `loadConfig` 可调用它复用默认结果,再做二次加工。
529
+ */
530
+ defaultLoadConfig: () => Promise<FormConfig>;
531
+ }
532
+
533
+ /**
534
+ * 自定义 FormConfig 加载逻辑。传入后将接管内置的按 `category` 取配置逻辑,
535
+ * 可通过 `ctx.defaultLoadConfig()` 复用默认结果再做二次加工。
536
+ */
537
+ export type CompareFormLoadConfig = (ctx: CompareFormLoadConfigContext) => FormConfig | Promise<FormConfig>;
538
+ // #endregion CompareForm
539
+
466
540
  // #region SideItemKey
467
541
  export enum SideItemKey {
468
542
  COMPONENT_LIST = 'component-list',
@@ -627,18 +701,169 @@ export interface StepValue {
627
701
  indexMap?: Record<string, number>;
628
702
  /** opType 'remove': 被删除的节点及其位置信息 */
629
703
  removedItems?: { node: MNode; parentId: Id; index: number }[];
630
- /** opType 'update': 变更前后的节点快照 */
631
- updatedItems?: { oldNode: MNode; newNode: MNode }[];
704
+ /**
705
+ * opType 'update': 变更前后的节点快照
706
+ *
707
+ * `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
708
+ * 缺省(未传 / 空数组)才退化为整节点替换。
709
+ */
710
+ updatedItems?: { oldNode: MNode; newNode: MNode; changeRecords?: ChangeRecord[] }[];
711
+ /**
712
+ * 调用方可选传入的人类可读描述(如「调整按钮颜色」),用于历史面板展示。
713
+ * 不影响 undo/redo 行为;缺省时面板会根据节点 / propPath 自动生成描述。
714
+ */
715
+ historyDescription?: string;
716
+ /**
717
+ * 入栈时间戳(毫秒)。在 historyService.push 时自动写入(若调用方未指定),仅用于历史面板展示。
718
+ */
719
+ timestamp?: number;
632
720
  }
633
721
  // #endregion StepValue
634
722
 
723
+ // #region CodeBlockStepValue
724
+ /**
725
+ * 代码块历史记录条目。按 codeBlock.id 分组保存到 historyState.codeBlockState。
726
+ * - 新增:oldContent = null,newContent = 新内容
727
+ * - 更新:oldContent / newContent 都为对应内容
728
+ * - 删除:newContent = null,oldContent = 删除前内容
729
+ */
730
+ export interface CodeBlockStepValue {
731
+ /** 关联的代码块 id */
732
+ id: Id;
733
+ /** 变更前的代码块内容,新增时为 null */
734
+ oldContent: CodeBlockContent | null;
735
+ /** 变更后的代码块内容,删除时为 null */
736
+ newContent: CodeBlockContent | null;
737
+ /**
738
+ * form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
739
+ * 缺省才退化为整内容替换。新增/删除场景通常无 changeRecords。
740
+ */
741
+ changeRecords?: ChangeRecord[];
742
+ /** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
743
+ historyDescription?: string;
744
+ /** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
745
+ timestamp?: number;
746
+ }
747
+ // #endregion CodeBlockStepValue
748
+
749
+ // #region DataSourceStepValue
750
+ /**
751
+ * 数据源历史记录条目。按 dataSource.id 分组保存到 historyState.dataSourceState。
752
+ * - 新增:oldSchema = null,newSchema = 新 schema
753
+ * - 更新:oldSchema / newSchema 都为对应 schema
754
+ * - 删除:newSchema = null,oldSchema = 删除前 schema
755
+ */
756
+ export interface DataSourceStepValue {
757
+ /** 关联的数据源 id */
758
+ id: Id;
759
+ /** 变更前的数据源 schema,新增时为 null */
760
+ oldSchema: DataSourceSchema | null;
761
+ /** 变更后的数据源 schema,删除时为 null */
762
+ newSchema: DataSourceSchema | null;
763
+ /**
764
+ * form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
765
+ * 缺省才退化为整 schema 替换。新增/删除场景通常无 changeRecords。
766
+ */
767
+ changeRecords?: ChangeRecord[];
768
+ /** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
769
+ historyDescription?: string;
770
+ /** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
771
+ timestamp?: number;
772
+ }
773
+ // #endregion DataSourceStepValue
774
+
635
775
  export interface HistoryState {
636
776
  pageId?: Id;
637
777
  pageSteps: Record<Id, UndoRedo<StepValue>>;
638
778
  canRedo: boolean;
639
779
  canUndo: boolean;
780
+ /**
781
+ * 代码块历史栈,按 codeBlock.id 分组(每个代码块独立一份 UndoRedo)。
782
+ * 与页面/节点无关,支持独立 undo/redo。
783
+ */
784
+ codeBlockState: Record<Id, UndoRedo<CodeBlockStepValue>>;
785
+ /**
786
+ * 数据源历史栈,按 dataSource.id 分组(每个数据源独立一份 UndoRedo)。
787
+ * 与页面/节点无关,支持独立 undo/redo。
788
+ */
789
+ dataSourceState: Record<Id, UndoRedo<DataSourceStepValue>>;
790
+ }
791
+
792
+ // #region HistoryListEntry
793
+ /**
794
+ * 历史面板用:当前页面的一条历史步骤(包含位置和是否已应用)。
795
+ */
796
+ export interface PageHistoryStepEntry {
797
+ /** 步骤内容 */
798
+ step: StepValue;
799
+ /** 在所属栈中的索引(0 为最早) */
800
+ index: number;
801
+ /** 是否处于"已应用"段(即位于栈游标之前)。撤销后变为 false。 */
802
+ applied: boolean;
803
+ /** 是否为当前所在的步骤(栈中最近一次已应用的那一步,即 index === cursor - 1)。 */
804
+ isCurrent?: boolean;
805
+ }
806
+
807
+ /**
808
+ * 页面历史面板分组。
809
+ * - 连续修改同一目标节点(updatedItems[0].oldNode.id 一致)的 'update' 步骤合并成一组;
810
+ * - 多节点更新 / add / remove 始终独立成组(无法明确归属单一目标)。
811
+ * - targetId 为 undefined 表示"无明确目标"(如 add/remove/多节点 update),不参与合并。
812
+ */
813
+ export interface PageHistoryGroup {
814
+ kind: 'page';
815
+ /** 所属页面 id */
816
+ pageId: Id;
817
+ /** 该分组的操作类型 */
818
+ opType: HistoryOpType;
819
+ /**
820
+ * 合并的目标节点 id;只有"单节点 update"才有值,并按此 id 与相邻同 id 的 update 合并。
821
+ * undefined 表示该分组不可被合并(add / remove / 多节点 update)。
822
+ */
823
+ targetId?: Id;
824
+ /** 目标节点的可读名(取最后一步的 newNode.name/type/id) */
825
+ targetName?: string;
826
+ /** 组内所有步骤,按时间正序 */
827
+ steps: PageHistoryStepEntry[];
828
+ /** 组内最后一步是否已应用 */
829
+ applied: boolean;
830
+ /** 是否为当前所在的分组(包含栈中最近一次已应用步骤的那一组)。 */
831
+ isCurrent?: boolean;
832
+ }
833
+
834
+ /**
835
+ * 代码块历史面板分组。
836
+ * - 同一 codeBlockId 的栈内,相邻的 'update' 操作会合并成一个 group;
837
+ * - 'add' / 'remove' 始终独立成组(语义上是一次性事件)。
838
+ */
839
+ export interface CodeBlockHistoryGroup {
840
+ kind: 'code-block';
841
+ /** 关联的 codeBlock id */
842
+ id: Id;
843
+ /** 该分组的操作类型 */
844
+ opType: HistoryOpType;
845
+ /** 组内所有步骤,按时间正序 */
846
+ steps: { step: CodeBlockStepValue; index: number; applied: boolean; isCurrent?: boolean }[];
847
+ /** 组内最后一步是否已应用,用于整组的状态展示 */
848
+ applied: boolean;
849
+ /** 是否为当前所在的分组(包含该栈最近一次已应用步骤的那一组)。 */
850
+ isCurrent?: boolean;
640
851
  }
641
852
 
853
+ /**
854
+ * 数据源历史面板分组,结构同 CodeBlockHistoryGroup。
855
+ */
856
+ export interface DataSourceHistoryGroup {
857
+ kind: 'data-source';
858
+ id: Id;
859
+ opType: HistoryOpType;
860
+ steps: { step: DataSourceStepValue; index: number; applied: boolean; isCurrent?: boolean }[];
861
+ applied: boolean;
862
+ /** 是否为当前所在的分组(包含该栈最近一次已应用步骤的那一组)。 */
863
+ isCurrent?: boolean;
864
+ }
865
+ // #endregion HistoryListEntry
866
+
642
867
  export enum KeyBindingCommand {
643
868
  /** 复制 */
644
869
  COPY_NODE = 'tmagic-system-copy-node',
@@ -874,12 +1099,50 @@ export const canUsePluginMethods = {
874
1099
 
875
1100
  export type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
876
1101
 
1102
+ /**
1103
+ * 历史记录写入相关的通用配置(codeBlock / dataSource / editor 共用)
1104
+ * - doNotPushHistory: 操作完成后是否不要将本次操作压入历史栈(撤销/重做记录),默认 false
1105
+ * - historyDescription: 入栈时附带的人类可读描述,用于历史面板展示;不影响 undo/redo 行为,缺省时面板会自动生成描述
1106
+ */
1107
+ export interface HistoryOpOptions {
1108
+ doNotPushHistory?: boolean;
1109
+ historyDescription?: string;
1110
+ }
1111
+
1112
+ /**
1113
+ * 在 HistoryOpOptions 基础上携带 form 端 propPath/value 变更记录,
1114
+ * 用于历史记录的精细化撤销/重做(按 propPath 局部 patch)。
1115
+ */
1116
+ export interface HistoryOpOptionsWithChangeRecords extends HistoryOpOptions {
1117
+ changeRecords?: ChangeRecord[];
1118
+ }
1119
+
877
1120
  /**
878
1121
  * DSL 修改类操作的通用配置
879
1122
  * - doNotSelect: 操作后是否不要自动触发选中(不调用 this.select / this.multiSelect / stage.select / stage.multiSelect)
880
1123
  * - doNotSwitchPage: 操作若会引发当前页面切换(如新增 / 删除 / 跨页移动),是否跳过这次切换
881
1124
  */
882
- export type DslOpOptions = {
1125
+ export interface DslOpOptions extends HistoryOpOptions {
883
1126
  doNotSelect?: boolean;
884
1127
  doNotSwitchPage?: boolean;
885
- };
1128
+ }
1129
+
1130
+ /** 差异对话框的入参 */
1131
+ export interface DiffDialogPayload {
1132
+ /** 表单类别 */
1133
+ category: CompareCategory;
1134
+ /** 节点类型 / 数据源类型 */
1135
+ type?: string;
1136
+ /** 代码块场景下的数据源类型 */
1137
+ dataSourceType?: string;
1138
+ /** 该 step 修改前的值(oldNode / oldSchema / oldContent) */
1139
+ lastValue: Record<string, any>;
1140
+ /** 该 step 修改后的值(newNode / newSchema / newContent) */
1141
+ value: Record<string, any>;
1142
+ /** 当前编辑器中实际的最新值;不传或为 null 时禁用「与当前对比」 */
1143
+ currentValue?: Record<string, any> | null;
1144
+ /** 用于标题展示的目标名称 */
1145
+ targetLabel?: string;
1146
+ /** 用于标题展示的目标 id */
1147
+ id?: string | number;
1148
+ }
@@ -0,0 +1,150 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
3
+ *
4
+ * Copyright (C) 2025 Tencent. All rights reserved.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import { tMagicMessage } from '@tmagic/design';
20
+ import { defineFormConfig, defineFormItem, type FormConfig, type TableColumnConfig } from '@tmagic/form';
21
+
22
+ import { getEditorConfig } from './config';
23
+
24
+ /**
25
+ * 代码块编辑表单配置的统一入口。
26
+ *
27
+ * 用于:
28
+ * - `CodeBlockEditor.vue`:作为代码块新增/编辑的表单结构;
29
+ * - `CompareForm.vue`:在历史差异对比中以"代码块"形态展示前后值。
30
+ *
31
+ * 通过参数控制两端的细微差异(是否启用必填校验 / vs-code onChange 校验 / dataSource 相关字段显示规则等),
32
+ * 避免两边同步两份相同的 schema 而造成漂移。
33
+ */
34
+ export interface GetCodeBlockFormConfigOptions {
35
+ /**
36
+ * 自定义 `params` 表格的"参数类型"列配置。优先级高于内置默认值;
37
+ * 通常由 `codeBlockService.getParamsColConfig()` 注入业务侧的扩展。
38
+ */
39
+ paramColConfig?: TableColumnConfig;
40
+ /**
41
+ * 是否在「数据源代码块」语境下使用:
42
+ * - true:`执行时机` 字段会展示,`请求前 / 请求后` 选项仅在 `dataSourceType !== 'base'` 时出现;
43
+ * - false:`执行时机` 字段隐藏(普通代码块没有时机概念)。
44
+ *
45
+ * 注意这里以函数形式传入是为了让 `display` / `options` 在每次渲染时实时取值,
46
+ * 例如外部 `props.isDataSource` 切换或 `dataSourceType` 变更都能立即生效。
47
+ */
48
+ isDataSource?: () => boolean;
49
+ /** 当 isDataSource 为 true 时使用:返回当前数据源类型(`base` / `http` / ...),决定时机选项是否包含请求前后。 */
50
+ dataSourceType?: () => string | undefined;
51
+ /** vs-code 编辑器的额外 monaco options。一般传 `inject('codeOptions', {})` 的结果。 */
52
+ codeOptions?: Record<string, any>;
53
+ /**
54
+ * 是否启用编辑态特性:
55
+ * - true(默认):`name` 字段加必填校验;vs-code 的 `onChange` 会调用 `parseDSL` 检查语法,出错时弹消息并抛错;
56
+ * - false:纯只读/对比场景,不加校验逻辑。
57
+ */
58
+ editable?: boolean;
59
+ }
60
+
61
+ /** 默认的"参数类型"列配置:数字 / 字符串 / 组件 三选一。 */
62
+ const defaultParamColConfig = () =>
63
+ defineFormItem<TableColumnConfig>({
64
+ type: 'row',
65
+ label: '参数类型',
66
+ items: [
67
+ {
68
+ text: '参数类型',
69
+ labelWidth: '70px',
70
+ type: 'select',
71
+ name: 'type',
72
+ options: [
73
+ { text: '数字', label: '数字', value: 'number' },
74
+ { text: '字符串', label: '字符串', value: 'text' },
75
+ { text: '组件', label: '组件', value: 'ui-select' },
76
+ ],
77
+ },
78
+ ],
79
+ });
80
+
81
+ /**
82
+ * 生成代码块表单配置。返回的是普通对象数组,可直接传给 `<MForm :config>`;
83
+ * 如需响应式的 props 变化(如切换 dataSourceType),调用方在 `computed` 中再次调用本函数即可。
84
+ */
85
+ export const getCodeBlockFormConfig = (options: GetCodeBlockFormConfigOptions = {}): FormConfig => {
86
+ const { paramColConfig, isDataSource, dataSourceType, codeOptions = {}, editable = true } = options;
87
+
88
+ return defineFormConfig([
89
+ {
90
+ text: '名称',
91
+ name: 'name',
92
+ ...(editable ? { rules: [{ required: true, message: '请输入名称', trigger: 'blur' }] } : {}),
93
+ },
94
+ {
95
+ text: '描述',
96
+ name: 'desc',
97
+ },
98
+ {
99
+ text: '执行时机',
100
+ name: 'timing',
101
+ type: 'select',
102
+ options: () => {
103
+ const list = [
104
+ { text: '初始化前', value: 'beforeInit' },
105
+ { text: '初始化后', value: 'afterInit' },
106
+ ];
107
+ if (dataSourceType?.() !== 'base') {
108
+ list.push({ text: '请求前', value: 'beforeRequest' });
109
+ list.push({ text: '请求后', value: 'afterRequest' });
110
+ }
111
+ return list;
112
+ },
113
+ display: () => Boolean(isDataSource?.()),
114
+ },
115
+ {
116
+ type: 'table',
117
+ border: true,
118
+ text: '参数',
119
+ enableFullscreen: false,
120
+ enableToggleMode: false,
121
+ name: 'params',
122
+ dropSort: false,
123
+ items: [
124
+ { type: 'text', label: '参数名', name: 'name' },
125
+ { type: 'text', label: '描述', name: 'extra' },
126
+ paramColConfig || defaultParamColConfig(),
127
+ ],
128
+ },
129
+ {
130
+ name: 'content',
131
+ type: 'vs-code',
132
+ options: codeOptions,
133
+ autosize: { minRows: 10, maxRows: 30 },
134
+ ...(editable
135
+ ? {
136
+ onChange: (_formState: any, code: string) => {
137
+ try {
138
+ // 检测 js 代码是否存在语法错误
139
+ getEditorConfig('parseDSL')(code);
140
+ return code;
141
+ } catch (error: any) {
142
+ tMagicMessage.error(error.message);
143
+ throw error;
144
+ }
145
+ },
146
+ }
147
+ : {}),
148
+ },
149
+ ]) as FormConfig;
150
+ };
@@ -1,7 +1,7 @@
1
1
  import { computed, markRaw, type ShallowRef } from 'vue';
2
2
  import { CopyDocument, Delete, DocumentCopy } from '@element-plus/icons-vue';
3
3
 
4
- import { Id, MContainer, NodeType } from '@tmagic/core';
4
+ import { cloneDeep, Id, MContainer, NodeType } from '@tmagic/core';
5
5
  import { calcValueByFontsize, isPage, isPageFragment } from '@tmagic/utils';
6
6
 
7
7
  import ContentMenu from '@editor/components/ContentMenu.vue';
@@ -59,14 +59,17 @@ export const usePasteMenu = (menu?: ShallowRef<InstanceType<typeof ContentMenu>
59
59
  },
60
60
  });
61
61
 
62
- const moveTo = (id: Id, { editorService }: Services) => {
62
+ const moveTo = async (id: Id, { editorService }: Services) => {
63
63
  const nodes = editorService.get('nodes') || [];
64
64
  const parent = editorService.getNodeById(id) as MContainer;
65
65
 
66
- if (!parent) return;
66
+ if (!parent || nodes.length === 0) return;
67
67
 
68
- editorService.add(nodes, parent);
69
- editorService.remove(nodes);
68
+ // 直接调用 moveToContainer:内部对多选场景已做合并,整批只产生一条历史记录。
69
+ // 不要再走 remove + add 两步,否则会被切成两条历史(且语义也不正确)。
70
+ await editorService.moveToContainer(cloneDeep(nodes), parent.id, {
71
+ doNotSwitchPage: true,
72
+ });
70
73
  };
71
74
 
72
75
  export const useMoveToMenu = ({ editorService }: Services): MenuButton => {
@@ -42,9 +42,11 @@ onmessage = (e) => {
42
42
  }
43
43
  }
44
44
 
45
- watcher.collectByCallback(mApp.items, undefined, ({ node, target }) => {
46
- watcher.collectItem(node, target, { pageId: node.id }, true);
47
- });
45
+ // worker target 均为新建(deps 为空),无需删除阶段,直接批量收集
46
+ const targets = watcher.getCollectableTargets();
47
+ for (const page of mApp.items) {
48
+ watcher.collectItems(page, targets, { pageId: page.id }, true);
49
+ }
48
50
 
49
51
  const data: Record<string, Record<Id, DepData>> = {
50
52
  [DepTargetType.DATA_SOURCE]: {},
@@ -18,6 +18,7 @@
18
18
 
19
19
  export * from './config';
20
20
  export * from './props';
21
+ export * from './code-block';
21
22
  export * from './logger';
22
23
  export * from './editor';
23
24
  export * from './operator';
@@ -75,5 +75,28 @@ export class UndoRedo<T = any> {
75
75
  }
76
76
  return cloneDeep(this.elementList[this.listCursor - 1]);
77
77
  }
78
+
79
+ /**
80
+ * 返回栈内全部元素的浅克隆数组(按时间顺序,索引 0 为最早一步)。
81
+ * 仅用于历史面板等只读展示场景,不应直接修改返回值。
82
+ */
83
+ public getElementList(): T[] {
84
+ return this.elementList.slice();
85
+ }
86
+
87
+ /**
88
+ * 当前游标位置:表示已应用的步骤数量。
89
+ * - cursor === 0 表示全部已撤销
90
+ * - cursor === length 表示已重做到末尾
91
+ * 历史面板用于区分"已应用 / 已撤销"两段。
92
+ */
93
+ public getCursor(): number {
94
+ return this.listCursor;
95
+ }
96
+
97
+ /** 栈内总步数。 */
98
+ public getLength(): number {
99
+ return this.elementList.length;
100
+ }
78
101
  }
79
102
  // #endregion UndoRedo