@tmagic/editor 1.8.0-beta.7 → 1.8.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +11 -3
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +24 -11
- package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/components/Icon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/Resizer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ScrollBar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ScrollViewer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/SearchInput.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/SplitView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/Tree.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/TreeNode.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +1 -1
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Block.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Flex.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Inline.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/InlineBlock.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/None.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Center.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Left.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Right.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/icons/CenterIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/icons/CodeIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/icons/FolderMinusIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/index.js +8 -4
- package/dist/es/layouts/AddPageBox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/layouts/NavMenuColumn.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 +1 -1
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +39 -27
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +10 -3
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +167 -302
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/composables.js +23 -55
- package/dist/es/layouts/history-list/useHistoryList.js +56 -0
- package/dist/es/layouts/history-list/useHistoryRevert.js +307 -0
- package/dist/es/layouts/page-bar/AddButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/PageBarScrollContainer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/PageList.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/Search.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceAddButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceList.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/Breadcrumb.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/Workspace.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/services/codeBlock.js +32 -28
- package/dist/es/services/dataSource.js +43 -34
- package/dist/es/services/editor.js +31 -26
- package/dist/es/services/history.js +268 -384
- package/dist/es/style.css +12 -0
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/history.js +35 -47
- package/dist/style.css +12 -0
- package/dist/tmagic-editor.umd.cjs +5524 -4807
- package/package.json +8 -8
- package/src/Editor.vue +10 -1
- package/src/components/CompareForm.vue +28 -7
- package/src/fields/DataSourceInput.vue +3 -3
- package/src/index.ts +4 -0
- package/src/layouts/NavMenu.vue +2 -2
- package/src/layouts/history-list/GroupRow.vue +10 -0
- package/src/layouts/history-list/HistoryDiffDialog.vue +8 -1
- package/src/layouts/history-list/HistoryListPanel.vue +64 -251
- package/src/layouts/history-list/PageTab.vue +4 -4
- package/src/layouts/history-list/composables.ts +52 -90
- package/src/layouts/history-list/useHistoryList.ts +60 -0
- package/src/layouts/history-list/useHistoryRevert.ts +406 -0
- package/src/services/codeBlock.ts +30 -29
- package/src/services/dataSource.ts +37 -37
- package/src/services/editor.ts +32 -29
- package/src/services/history.ts +340 -430
- package/src/theme/history-list-panel.scss +14 -0
- package/src/type.ts +185 -92
- package/src/utils/history.ts +52 -67
- package/types/index.d.ts +466 -297
|
@@ -330,6 +330,20 @@
|
|
|
330
330
|
font-weight: 400; // 防止被合并组头部的粗体继承
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
// 「操作人」徽标:浅蓝描边胶囊,弱化展示操作人,仅在 step.operator 有值时渲染。
|
|
334
|
+
.m-editor-history-list-item-operator {
|
|
335
|
+
flex: 0 0 auto;
|
|
336
|
+
padding: 0 6px;
|
|
337
|
+
border: 1px solid #c6e2ff;
|
|
338
|
+
border-radius: 8px;
|
|
339
|
+
font-size: 10px;
|
|
340
|
+
line-height: 14px;
|
|
341
|
+
color: #409eff;
|
|
342
|
+
background-color: #ecf5ff;
|
|
343
|
+
white-space: nowrap;
|
|
344
|
+
font-weight: 400; // 防止被合并组头部的粗体继承
|
|
345
|
+
}
|
|
346
|
+
|
|
333
347
|
// 「已保存」徽标:绿色实心胶囊,标记最近一次保存对应的历史记录(与 historyService.markSaved 对应)。
|
|
334
348
|
.m-editor-history-list-item-saved {
|
|
335
349
|
flex: 0 0 auto;
|
package/src/type.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type { Component } from 'vue';
|
|
19
|
+
import type { AppContext, Component } from 'vue';
|
|
20
20
|
import type EventEmitter from 'events';
|
|
21
21
|
import type * as Monaco from 'monaco-editor';
|
|
22
22
|
import type { default as Sortable, Options, SortableEvent } from 'sortablejs';
|
|
@@ -33,7 +33,7 @@ import type {
|
|
|
33
33
|
MPage,
|
|
34
34
|
MPageFragment,
|
|
35
35
|
} from '@tmagic/core';
|
|
36
|
-
import type { ChangeRecord, FormConfig, TableColumnConfig, TypeFunction } from '@tmagic/form';
|
|
36
|
+
import type { ChangeRecord, FormConfig, FormState, TableColumnConfig, TypeFunction } from '@tmagic/form';
|
|
37
37
|
import type StageCore from '@tmagic/stage';
|
|
38
38
|
import type {
|
|
39
39
|
CanDropIn,
|
|
@@ -780,13 +780,18 @@ export interface StepDiffItem<T = unknown> {
|
|
|
780
780
|
*
|
|
781
781
|
* 泛型 `T` 为 `diff` 中变化内容的快照类型(页面节点 `MNode` / 代码块 `CodeBlockContent` / 数据源 `DataSourceSchema`)。
|
|
782
782
|
*/
|
|
783
|
-
export interface BaseStepValue<T = unknown> {
|
|
783
|
+
export interface BaseStepValue<T = unknown, U extends Record<string, any> = {}> {
|
|
784
784
|
/**
|
|
785
785
|
* 历史记录唯一标识(uuid)。入栈时自动写入(若调用方未指定),
|
|
786
786
|
* 用于精确定位 / 引用某一条历史记录(如 revert、埋点、跨端同步等)。
|
|
787
|
-
*
|
|
787
|
+
* 注意与 `data.id`(关联的页面 / 代码块 / 数据源 id)区分。
|
|
788
788
|
*/
|
|
789
789
|
uuid: string;
|
|
790
|
+
/**
|
|
791
|
+
* 关联目标信息:`id` 为关联的页面 / 代码块 / 数据源等资源 id(也是历史栈的分组 key),
|
|
792
|
+
* `name` 为展示名。所有历史类型统一携带。
|
|
793
|
+
*/
|
|
794
|
+
data: { name: string; id: Id };
|
|
790
795
|
/** 操作类型:新增 / 删除 / 更新(三类历史记录统一携带)。 */
|
|
791
796
|
opType: HistoryOpType;
|
|
792
797
|
/**
|
|
@@ -820,64 +825,96 @@ export interface BaseStepValue<T = unknown> {
|
|
|
820
825
|
* 避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录。
|
|
821
826
|
*/
|
|
822
827
|
rootStep?: boolean;
|
|
828
|
+
/** 操作人 */
|
|
829
|
+
operator?: string;
|
|
830
|
+
/** 扩展信息 */
|
|
831
|
+
extra?: U;
|
|
823
832
|
}
|
|
824
833
|
// #endregion BaseStepValue
|
|
825
834
|
|
|
826
|
-
// #region
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
|
|
834
|
-
|
|
835
|
+
// #region StepExtra
|
|
836
|
+
/**
|
|
837
|
+
* 历史记录的扩展上下文({@link BaseStepValue.extra})。
|
|
838
|
+
* 内置字段供 `page` 类型在撤销 / 重做时恢复选区与受影响节点;扩展类型可自由附加其它键。
|
|
839
|
+
*/
|
|
840
|
+
export interface StepExtra {
|
|
841
|
+
/** 操作前选中的节点 ID,用于撤销后恢复选择状态(page 类型) */
|
|
842
|
+
selectedBefore?: Id[];
|
|
843
|
+
/** 操作后选中的节点 ID,用于重做后恢复选择状态(page 类型) */
|
|
844
|
+
selectedAfter?: Id[];
|
|
845
|
+
/** 本次操作涉及的节点 id 集合(page 类型) */
|
|
846
|
+
modifiedNodeIds?: Map<Id, Id>;
|
|
847
|
+
[key: string]: any;
|
|
835
848
|
}
|
|
849
|
+
// #endregion StepExtra
|
|
850
|
+
|
|
851
|
+
// #region StepValue
|
|
852
|
+
/**
|
|
853
|
+
* 页面节点历史记录条目(`diff` 内容为 {@link MNode})。结构已与代码块 / 数据源统一收敛到
|
|
854
|
+
* {@link BaseStepValue}:关联 id 见 `data.id`,选区等上下文见 `extra`。
|
|
855
|
+
*/
|
|
856
|
+
export type StepValue = BaseStepValue<MNode, StepExtra>;
|
|
836
857
|
// #endregion StepValue
|
|
837
858
|
|
|
838
859
|
// #region CodeBlockStepValue
|
|
839
860
|
/**
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新;
|
|
844
|
-
* - 删除(opType 'remove'):仅 `oldSchema`(删除前内容)。
|
|
861
|
+
* 代码块历史记录条目(`diff` 内容为 {@link CodeBlockContent}),按 `data.id`(codeBlock.id)
|
|
862
|
+
* 分组保存到 historyState.steps.codeBlock。结构与 {@link StepValue} / {@link DataSourceStepValue}
|
|
863
|
+
* 一致,仅 `diff` 快照类型不同。
|
|
845
864
|
*/
|
|
846
|
-
export
|
|
847
|
-
/** 关联的代码块 id */
|
|
848
|
-
id: Id;
|
|
849
|
-
}
|
|
865
|
+
export type CodeBlockStepValue = BaseStepValue<CodeBlockContent>;
|
|
850
866
|
// #endregion CodeBlockStepValue
|
|
851
867
|
|
|
852
868
|
// #region DataSourceStepValue
|
|
853
869
|
/**
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
* - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新;
|
|
858
|
-
* - 删除(opType 'remove'):仅 `oldSchema`(删除前 schema)。
|
|
870
|
+
* 数据源历史记录条目(`diff` 内容为 {@link DataSourceSchema}),按 `data.id`(dataSource.id)
|
|
871
|
+
* 分组保存到 historyState.steps.dataSource。结构与 {@link StepValue} / {@link CodeBlockStepValue}
|
|
872
|
+
* 一致,仅 `diff` 快照类型不同。
|
|
859
873
|
*/
|
|
860
|
-
export
|
|
861
|
-
/** 关联的数据源 id */
|
|
862
|
-
id: Id;
|
|
863
|
-
}
|
|
874
|
+
export type DataSourceStepValue = BaseStepValue<DataSourceSchema>;
|
|
864
875
|
// #endregion DataSourceStepValue
|
|
865
876
|
|
|
877
|
+
// #region HistorySteps
|
|
878
|
+
/**
|
|
879
|
+
* 历史记录类型标识:内置 `page` / `codeBlock` / `dataSource`,并允许业务扩展自定义类型。
|
|
880
|
+
* `(string & {})` 保留对内置字面量的智能提示,同时不限制扩展取值。
|
|
881
|
+
*/
|
|
882
|
+
export type HistoryStepType = 'page' | 'codeBlock' | 'dataSource' | (string & {});
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* 全部历史栈的统一容器,按「类型 -> id -> UndoRedo 栈」两级分组。
|
|
886
|
+
*
|
|
887
|
+
* - `page`:页面历史栈,按 page.id 分组(每页一份 UndoRedo);
|
|
888
|
+
* - `codeBlock`:代码块历史栈,按 codeBlock.id 分组;
|
|
889
|
+
* - `dataSource`:数据源历史栈,按 dataSource.id 分组;
|
|
890
|
+
* - 其余键:业务通过 {@link HistoryService.registerStepType} 注册的自定义历史类型。
|
|
891
|
+
*
|
|
892
|
+
* 所有类型(含扩展类型)一视同仁:均按 id 独立分栈、独立 undo/redo,且都可通过
|
|
893
|
+
* {@link HistoryService.setMarker} 在 index 0 种入 `initial` 基线(撤销 / 回滚不会越过该基线)。
|
|
894
|
+
*/
|
|
895
|
+
export interface HistorySteps {
|
|
896
|
+
page: Record<Id, UndoRedo<StepValue>>;
|
|
897
|
+
codeBlock: Record<Id, UndoRedo<CodeBlockStepValue>>;
|
|
898
|
+
dataSource: Record<Id, UndoRedo<DataSourceStepValue>>;
|
|
899
|
+
/** 扩展历史类型:按 id 分组的 UndoRedo 栈。 */
|
|
900
|
+
[stepType: string]: Record<Id, UndoRedo<any>>;
|
|
901
|
+
}
|
|
902
|
+
// #endregion HistorySteps
|
|
903
|
+
|
|
866
904
|
export interface HistoryState {
|
|
867
|
-
pageId?: Id;
|
|
868
|
-
pageSteps: Record<Id, UndoRedo<StepValue>>;
|
|
869
|
-
canRedo: boolean;
|
|
870
|
-
canUndo: boolean;
|
|
871
905
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
906
|
+
* 全部历史栈的统一容器(页面 / 代码块 / 数据源 / 扩展类型),见 {@link HistorySteps}。
|
|
907
|
+
* 各类型互不影响,支持按 id 独立 undo/redo;是否可撤销 / 重做改用 {@link HistoryService.canUndo} /
|
|
908
|
+
* {@link HistoryService.canRedo}(按 stepType + id 查询)替代旧的全局 canUndo / canRedo 字段。
|
|
874
909
|
*/
|
|
875
|
-
|
|
910
|
+
steps: HistorySteps;
|
|
876
911
|
/**
|
|
877
|
-
*
|
|
878
|
-
*
|
|
912
|
+
* 各历史类型的展示名称,用于历史面板({@link HistorySteps} 的 tab / 分组标题等)。
|
|
913
|
+
* 内置 `page` / `codeBlock` / `dataSource` 有默认中文名(页面 / 代码块 / 数据源),
|
|
914
|
+
* 扩展类型可通过 {@link HistoryService.registerStepType} 的 `name` 选项或
|
|
915
|
+
* {@link HistoryService.setStepName} 登记;读取请用 {@link HistoryService.getStepName}。
|
|
879
916
|
*/
|
|
880
|
-
|
|
917
|
+
stepNames: Record<string, string>;
|
|
881
918
|
}
|
|
882
919
|
|
|
883
920
|
// #region PersistedHistoryState
|
|
@@ -888,14 +925,16 @@ export interface HistoryState {
|
|
|
888
925
|
export interface PersistedHistoryState {
|
|
889
926
|
/** 快照结构版本号,便于后续兼容升级。 */
|
|
890
927
|
version: number;
|
|
891
|
-
/**
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
928
|
+
/**
|
|
929
|
+
* 全部历史栈的序列化快照,按「类型 -> id」两级分组,与 {@link HistorySteps} 对应。
|
|
930
|
+
* 内置 `page` / `codeBlock` / `dataSource`,并包含业务注册的扩展类型。
|
|
931
|
+
*/
|
|
932
|
+
steps: {
|
|
933
|
+
page: Record<Id, SerializedUndoRedo<StepValue>>;
|
|
934
|
+
codeBlock: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>;
|
|
935
|
+
dataSource: Record<Id, SerializedUndoRedo<DataSourceStepValue>>;
|
|
936
|
+
[stepType: string]: Record<Id, SerializedUndoRedo<any>>;
|
|
937
|
+
};
|
|
899
938
|
/** 保存时间戳(毫秒)。 */
|
|
900
939
|
savedAt: number;
|
|
901
940
|
}
|
|
@@ -923,9 +962,9 @@ export interface HistoryPersistOptions {
|
|
|
923
962
|
/**
|
|
924
963
|
* 历史面板用:当前页面的一条历史步骤(包含位置和是否已应用)。
|
|
925
964
|
*/
|
|
926
|
-
export interface
|
|
965
|
+
export interface HistoryStepEntry<T> {
|
|
927
966
|
/** 步骤内容 */
|
|
928
|
-
step:
|
|
967
|
+
step: T;
|
|
929
968
|
/** 在所属栈中的索引(0 为最早) */
|
|
930
969
|
index: number;
|
|
931
970
|
/** 是否处于"已应用"段(即位于栈游标之前)。撤销后变为 false。 */
|
|
@@ -935,55 +974,37 @@ export interface PageHistoryStepEntry {
|
|
|
935
974
|
}
|
|
936
975
|
|
|
937
976
|
/**
|
|
938
|
-
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
* - targetId
|
|
977
|
+
* 历史面板分组(页面 / 数据源 / 代码块 / 扩展类型统一结构)。
|
|
978
|
+
*
|
|
979
|
+
* 把指定历史栈的步骤列表按"目标"做相邻合并:
|
|
980
|
+
* - 连续修改同一目标(单实体 update,targetId 一致)的多步合并成一组,组内可展开查看每步;
|
|
981
|
+
* - add / remove / 多实体 update 始终独立成组(无法明确归属单一目标);
|
|
982
|
+
* - targetId 为 undefined 表示"无明确目标",不参与合并。
|
|
983
|
+
*
|
|
984
|
+
* 各类型仅 `kind` 与 step 快照类型不同,统一由泛型描述:
|
|
985
|
+
* - 页面:`HistoryGroup<StepValue>`,`kind: 'page'`,`id` 为 pageId,`targetId` 为被改节点 id;
|
|
986
|
+
* - 数据源:`HistoryGroup<DataSourceStepValue>`,`kind: 'data-source'`,`id` 为 dataSource.id;
|
|
987
|
+
* - 代码块:`HistoryGroup<CodeBlockStepValue>`,`kind: 'code-block'`,`id` 为 codeBlock.id。
|
|
942
988
|
*/
|
|
943
|
-
export interface
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
pageId
|
|
947
|
-
|
|
989
|
+
export interface HistoryGroup<T extends BaseStepValue = BaseStepValue> {
|
|
990
|
+
/** 历史类型标识:page / code-block / data-source(扩展类型同理)。 */
|
|
991
|
+
kind: string;
|
|
992
|
+
/** 所属栈 id(page 为 pageId,代码块 / 数据源为对应资源 id)。 */
|
|
993
|
+
id: Id;
|
|
994
|
+
/** 该分组的操作类型。 */
|
|
948
995
|
opType: HistoryOpType;
|
|
949
996
|
/**
|
|
950
|
-
*
|
|
951
|
-
* undefined 表示该分组不可被合并(add / remove /
|
|
997
|
+
* 合并的目标 id:仅"单实体 update"有值,并按此与相邻同 id 的 update 合并。
|
|
998
|
+
* undefined 表示该分组不可被合并(add / remove / 多实体 update)。
|
|
952
999
|
*/
|
|
953
1000
|
targetId?: Id;
|
|
954
|
-
/**
|
|
1001
|
+
/** 目标可读名(取最后一步快照的 name/type/id)。 */
|
|
955
1002
|
targetName?: string;
|
|
956
|
-
/**
|
|
957
|
-
steps: PageHistoryStepEntry[];
|
|
958
|
-
/** 组内最后一步是否已应用 */
|
|
959
|
-
applied: boolean;
|
|
960
|
-
/** 是否为当前所在的分组(包含栈中最近一次已应用步骤的那一组)。 */
|
|
961
|
-
isCurrent?: boolean;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* 数据源 / 代码块历史面板分组(按 id 分栈展示)。
|
|
966
|
-
* 二者结构完全一致,仅 `kind` 与 step 类型不同,统一由该泛型描述:
|
|
967
|
-
* - 数据源:`StackHistoryGroup<DataSourceStepValue, 'data-source'>`;
|
|
968
|
-
* - 代码块:`StackHistoryGroup<CodeBlockStepValue, 'code-block'>`。
|
|
969
|
-
*
|
|
970
|
-
* 每条操作记录独立成组,不做相邻合并(与页面历史 {@link PageHistoryGroup} 不同),故 `steps` 恒为单元素。
|
|
971
|
-
*/
|
|
972
|
-
export interface StackHistoryGroup<
|
|
973
|
-
T extends BaseStepValue = BaseStepValue,
|
|
974
|
-
K extends 'code-block' | 'data-source' = 'code-block' | 'data-source',
|
|
975
|
-
> {
|
|
976
|
-
/** 区分代码块 / 数据源。 */
|
|
977
|
-
kind: K;
|
|
978
|
-
/** 关联的代码块 / 数据源 id。 */
|
|
979
|
-
id: Id;
|
|
980
|
-
/** 该分组的操作类型。 */
|
|
981
|
-
opType: HistoryOpType;
|
|
982
|
-
/** 组内所有步骤,按时间正序(不做相邻合并,恒为单元素)。 */
|
|
1003
|
+
/** 组内所有步骤,按时间正序。 */
|
|
983
1004
|
steps: { step: T; index: number; applied: boolean; isCurrent?: boolean }[];
|
|
984
|
-
/**
|
|
1005
|
+
/** 组内最后一步是否已应用。 */
|
|
985
1006
|
applied: boolean;
|
|
986
|
-
/**
|
|
1007
|
+
/** 是否为当前所在的分组(包含栈中最近一次已应用步骤的那一组)。 */
|
|
987
1008
|
isCurrent?: boolean;
|
|
988
1009
|
}
|
|
989
1010
|
// #endregion HistoryListEntry
|
|
@@ -1199,6 +1220,21 @@ export interface EditorEvents {
|
|
|
1199
1220
|
'history-change': [data: MPage | MPageFragment];
|
|
1200
1221
|
}
|
|
1201
1222
|
|
|
1223
|
+
export interface HistoryEvents {
|
|
1224
|
+
change: [
|
|
1225
|
+
state: BaseStepValue | StepValue | CodeBlockStepValue | DataSourceStepValue,
|
|
1226
|
+
stepType: HistoryStepType,
|
|
1227
|
+
id: Id,
|
|
1228
|
+
];
|
|
1229
|
+
'code-block-history-change': [id: Id, state: CodeBlockStepValue];
|
|
1230
|
+
'data-source-history-change': [id: Id, state: DataSourceStepValue];
|
|
1231
|
+
'restore-from-indexed-db': [snapshot: PersistedHistoryState | null];
|
|
1232
|
+
'save-to-indexed-db': [snapshot: PersistedHistoryState];
|
|
1233
|
+
'mark-saved': [{ kind: HistoryStepType; id?: Id }];
|
|
1234
|
+
clear: [{ id: Id; stepType: HistoryStepType }];
|
|
1235
|
+
'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }];
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1202
1238
|
export const canUsePluginMethods = {
|
|
1203
1239
|
async: [
|
|
1204
1240
|
'getLayout',
|
|
@@ -1318,3 +1354,60 @@ export interface HistoryBucketConfig<T extends BaseStepValue = BaseStepValue> ex
|
|
|
1318
1354
|
/** 是否支持「跳转到该记录」(goto),默认 true。 */
|
|
1319
1355
|
gotoEnabled?: boolean;
|
|
1320
1356
|
}
|
|
1357
|
+
|
|
1358
|
+
export interface UseHistoryRevertOptions {
|
|
1359
|
+
/**
|
|
1360
|
+
* 父级应用上下文,用于让动态挂载的「差异确认弹窗」继承全局组件 / 指令 / provide / 插件
|
|
1361
|
+
* (Element Plus、@tmagic/form 字段组件等)。未显式传入时,会自动取调用方所在组件的 appContext
|
|
1362
|
+
* (`getCurrentInstance()?.appContext`)。业务方若在组件 setup 之外调用,需手动传入(如 `editorApp._context`)。
|
|
1363
|
+
*/
|
|
1364
|
+
appContext?: AppContext | null;
|
|
1365
|
+
/**
|
|
1366
|
+
* 透传给差异确认弹窗的 `extendState`(即 Editor 的 `extendFormState`),
|
|
1367
|
+
* 使对比表单中依赖业务上下文的 `display` / `disabled` 等 filterFunction 正常工作。
|
|
1368
|
+
*/
|
|
1369
|
+
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
1370
|
+
/**
|
|
1371
|
+
* 返回 PropsPanel 主属性表单(FormPanel -> MForm)的 formState。
|
|
1372
|
+
* 仅页面历史「查看差异 / 回滚确认」场景会使用该 formState 覆盖 CompareForm 中同名扩展字段,
|
|
1373
|
+
* 以保证两处 filterFunction 读取到一致的运行态上下文。
|
|
1374
|
+
*/
|
|
1375
|
+
getPropsPanelFormState?: () => FormState | undefined;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* 业务自有历史(如管理台「模块」)做差异对比时所需的额外渲染入参。
|
|
1380
|
+
* 内置的页面 / 数据源 / 代码块按 `category` 自动取表单配置,无需传这些;
|
|
1381
|
+
* 业务自有类别可通过 `loadConfig` 注入自定义表单配置加载逻辑。
|
|
1382
|
+
*/
|
|
1383
|
+
export interface CustomDiffFormOptions {
|
|
1384
|
+
/**
|
|
1385
|
+
* 自定义差异表单配置加载逻辑(如「模块」按 c_type 重建表单配置),
|
|
1386
|
+
* 透传给弹窗内部的 CompareForm;缺省时按 `category` 走内置加载。
|
|
1387
|
+
*/
|
|
1388
|
+
loadConfig?: CompareFormLoadConfig;
|
|
1389
|
+
/** 需要走 self diff 的字段类型(如模块的 mod-cond)。 */
|
|
1390
|
+
selfDiffFieldTypes?: string[];
|
|
1391
|
+
/**
|
|
1392
|
+
* 可选:外部提供的 formState(通常来自 PropsPanel 主表单),
|
|
1393
|
+
* 对比弹窗会用它覆盖 CompareForm 中同名扩展字段,避免上下文不一致。
|
|
1394
|
+
*/
|
|
1395
|
+
compareFormState?: FormState;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* 业务自有历史复用「单步回滚」交互({@link useHistoryRevert} 的 `confirmAndRevert`)的入参。
|
|
1400
|
+
* 与内置页面 / 数据源 / 代码块回滚共用「目标校验 → 差异/二次确认弹窗 → 反向回滚」流程,
|
|
1401
|
+
* 业务方只需提供:差异弹窗入参(可选)、表单配置加载(可选)、实际回滚执行函数。
|
|
1402
|
+
*/
|
|
1403
|
+
export interface ConfirmAndRevertOptions<T = unknown> extends CustomDiffFormOptions {
|
|
1404
|
+
/**
|
|
1405
|
+
* 差异确认弹窗入参;可对比的步骤(单实体 update)传入后弹差异确认弹窗,
|
|
1406
|
+
* 传 null / 省略则退化为普通二次确认框(add / remove / 不可对比)。
|
|
1407
|
+
*/
|
|
1408
|
+
diffPayload?: DiffDialogPayload | null;
|
|
1409
|
+
/** 回滚前置校验:返回 true 表示目标数据已删除等不可回滚,给出统一提示并中止。 */
|
|
1410
|
+
isTargetMissing?: () => boolean;
|
|
1411
|
+
/** 用户确认后执行的实际回滚逻辑。 */
|
|
1412
|
+
revert: () => T | Promise<T>;
|
|
1413
|
+
}
|
package/src/utils/history.ts
CHANGED
|
@@ -23,15 +23,7 @@ import type { Id } from '@tmagic/core';
|
|
|
23
23
|
import type { ChangeRecord } from '@tmagic/form';
|
|
24
24
|
import { guid } from '@tmagic/utils';
|
|
25
25
|
|
|
26
|
-
import type {
|
|
27
|
-
BaseStepValue,
|
|
28
|
-
HistoryOpSource,
|
|
29
|
-
PageHistoryGroup,
|
|
30
|
-
PageHistoryStepEntry,
|
|
31
|
-
StackHistoryGroup,
|
|
32
|
-
StepDiffItem,
|
|
33
|
-
StepValue,
|
|
34
|
-
} from '@editor/type';
|
|
26
|
+
import type { BaseStepValue, HistoryGroup, StepDiffItem } from '@editor/type';
|
|
35
27
|
|
|
36
28
|
import { UndoRedo } from './undo-redo';
|
|
37
29
|
|
|
@@ -71,17 +63,20 @@ export const detectStackOpType = (oldVal: unknown, newVal: unknown): 'add' | 're
|
|
|
71
63
|
*
|
|
72
64
|
* - `add`:oldValue = null;`remove`:newValue = null;`update`:两者都有,可带 changeRecords 做局部更新。
|
|
73
65
|
* - 内容会做 cloneDeep 防止后续被外部引用篡改;opType 依据 old/new 是否为 null 推断。
|
|
74
|
-
* - 仅负责构造 step 并返回,入栈与事件 emit
|
|
66
|
+
* - 仅负责构造 step 并返回,入栈与事件 emit 由统一的 history.push(stepType, step, id) 处理。
|
|
75
67
|
* - 不直接驱动业务 service,调用方负责实际写回。
|
|
76
68
|
*/
|
|
77
|
-
export const createStackStep = <T, S extends BaseStepValue<T
|
|
69
|
+
export const createStackStep = <T, S extends BaseStepValue<T>>(
|
|
78
70
|
id: Id,
|
|
79
|
-
payload
|
|
71
|
+
// payload 以 {@link BaseStepValue} 为基础:透传字段(historyDescription / source / operator / rootStep / extra)
|
|
72
|
+
// 随 BaseStepValue 演进自动同步,原样写入 step;自动生成字段(uuid / data / opType / diff / timestamp / saved)
|
|
73
|
+
// 从 payload 中排除,由本方法内部构造。oldValue / newValue / changeRecords / name 为构造 diff 与 data 用的输入。
|
|
74
|
+
payload: Omit<BaseStepValue<T>, 'uuid' | 'data' | 'opType' | 'diff' | 'timestamp' | 'saved'> & {
|
|
80
75
|
oldValue: T | null;
|
|
81
76
|
newValue: T | null;
|
|
82
77
|
changeRecords?: ChangeRecord[];
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
/** 展示名(缺省时从快照 name / title 推断)。 */
|
|
79
|
+
name?: string;
|
|
85
80
|
},
|
|
86
81
|
): S | null => {
|
|
87
82
|
if (!id) return null;
|
|
@@ -90,10 +85,13 @@ export const createStackStep = <T, S extends BaseStepValue<T> & { id: Id }>(
|
|
|
90
85
|
const newSchema = payload.newValue ? cloneDeep(payload.newValue) : null;
|
|
91
86
|
const changeRecords = payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined;
|
|
92
87
|
const opType = detectStackOpType(payload.oldValue, payload.newValue);
|
|
88
|
+
// 展示名:代码块取 name,数据源取 title,取不到则留空(不影响 undo/redo,仅用于展示)。
|
|
89
|
+
const schema = (payload.newValue ?? payload.oldValue) as { name?: string; title?: string } | null;
|
|
90
|
+
const name = payload.name ?? schema?.name ?? schema?.title ?? '';
|
|
93
91
|
|
|
94
|
-
const step: BaseStepValue<T>
|
|
92
|
+
const step: BaseStepValue<T> = {
|
|
95
93
|
uuid: guid(),
|
|
96
|
-
id,
|
|
94
|
+
data: { name, id },
|
|
97
95
|
opType,
|
|
98
96
|
diff: [
|
|
99
97
|
{
|
|
@@ -104,7 +102,10 @@ export const createStackStep = <T, S extends BaseStepValue<T> & { id: Id }>(
|
|
|
104
102
|
],
|
|
105
103
|
historyDescription: payload.historyDescription,
|
|
106
104
|
source: payload.source,
|
|
105
|
+
operator: payload.operator,
|
|
106
|
+
rootStep: payload.rootStep,
|
|
107
107
|
timestamp: Date.now(),
|
|
108
|
+
extra: payload.extra,
|
|
108
109
|
};
|
|
109
110
|
|
|
110
111
|
return step as S;
|
|
@@ -121,61 +122,41 @@ export const markStackSaved = <S extends { saved?: boolean }>(undoRedo?: UndoRed
|
|
|
121
122
|
};
|
|
122
123
|
|
|
123
124
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
125
|
+
* 把单个历史栈(页面 / 代码块 / 数据源 / 扩展类型)的步骤列表按"目标"做相邻合并:
|
|
126
|
+
* - 单实体的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group,组内可展开查看每步;
|
|
127
|
+
* - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单实体修改组);
|
|
128
|
+
* - 多实体 'update'(如页面批量改属性)也独立成组(无明确单一目标,避免误合并)。
|
|
126
129
|
*
|
|
127
|
-
*
|
|
130
|
+
* 各类型行为完全一致,仅 `kind` 与 step 快照类型不同,统一由本方法处理。
|
|
128
131
|
*/
|
|
129
|
-
export const
|
|
130
|
-
kind:
|
|
132
|
+
export const mergeSteps = <T extends BaseStepValue>(
|
|
133
|
+
kind: string,
|
|
131
134
|
id: Id,
|
|
132
|
-
list:
|
|
135
|
+
list: T[],
|
|
133
136
|
cursor: number,
|
|
134
|
-
):
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
const applied = index < cursor;
|
|
138
|
-
const isCurrent = index === currentIndex;
|
|
139
|
-
return {
|
|
140
|
-
kind,
|
|
141
|
-
id,
|
|
142
|
-
opType: step.opType,
|
|
143
|
-
steps: [{ step, index, applied, isCurrent }],
|
|
144
|
-
applied,
|
|
145
|
-
isCurrent,
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 把页面栈拆成若干 group:
|
|
152
|
-
* - 单节点的 'update' 按 targetId 与相邻同 targetId 的 update 合并到一个 group;
|
|
153
|
-
* - 'add' / 'remove' 始终独立成组(语义上是结构变更,不应被收纳进单节点修改组);
|
|
154
|
-
* - 多节点 'update'(如批量改属性)也独立成组(无明确单一目标,避免误合并)。
|
|
155
|
-
*/
|
|
156
|
-
export const mergePageSteps = (pageId: Id, list: StepValue[], cursor: number): PageHistoryGroup[] => {
|
|
157
|
-
const groups: PageHistoryGroup[] = [];
|
|
158
|
-
let current: PageHistoryGroup | null = null;
|
|
137
|
+
): HistoryGroup<T>[] => {
|
|
138
|
+
const groups: HistoryGroup<T>[] = [];
|
|
139
|
+
let current: HistoryGroup<T> | null = null;
|
|
159
140
|
const currentIndex = cursor - 1;
|
|
160
141
|
list.forEach((step, index) => {
|
|
161
142
|
const applied = index < cursor;
|
|
162
143
|
const isCurrent = index === currentIndex;
|
|
163
|
-
const targetId =
|
|
164
|
-
const targetName =
|
|
165
|
-
const entry
|
|
144
|
+
const targetId = detectTargetId(step);
|
|
145
|
+
const targetName = detectTargetName(step);
|
|
146
|
+
const entry = { step, index, applied, isCurrent };
|
|
166
147
|
|
|
167
|
-
// 仅"
|
|
148
|
+
// 仅"单实体 update"参与合并;其它情形(add/remove/多实体 update)始终独立成组。
|
|
168
149
|
const mergeable = step.opType === 'update' && targetId !== undefined;
|
|
169
150
|
if (mergeable && current?.opType === 'update' && current.targetId === targetId) {
|
|
170
151
|
current.steps.push(entry);
|
|
171
152
|
current.applied = applied;
|
|
172
153
|
if (isCurrent) current.isCurrent = true;
|
|
173
|
-
//
|
|
154
|
+
// 保持目标名为最近一次的(重命名时也能反映)
|
|
174
155
|
if (targetName) current.targetName = targetName;
|
|
175
156
|
} else {
|
|
176
157
|
current = {
|
|
177
|
-
kind
|
|
178
|
-
|
|
158
|
+
kind,
|
|
159
|
+
id,
|
|
179
160
|
opType: step.opType,
|
|
180
161
|
targetId: mergeable ? targetId : undefined,
|
|
181
162
|
targetName,
|
|
@@ -190,37 +171,40 @@ export const mergePageSteps = (pageId: Id, list: StepValue[], cursor: number): P
|
|
|
190
171
|
};
|
|
191
172
|
|
|
192
173
|
/**
|
|
193
|
-
* 解析
|
|
194
|
-
* -
|
|
195
|
-
*
|
|
174
|
+
* 解析 step 中的"目标 id"用于合并:
|
|
175
|
+
* - 单实体 update:取唯一一项 diff 的快照 id;快照无 id 时(如 CodeBlockContent)回退到 `step.data.id`
|
|
176
|
+
* (即资源 id),使代码块 / 数据源同样能按资源合并相邻 update;
|
|
177
|
+
* - 其它情形(多实体 update / add / remove):返回 undefined,表示不参与合并。
|
|
196
178
|
*/
|
|
197
|
-
export const
|
|
179
|
+
export const detectTargetId = (step: BaseStepValue): Id | undefined => {
|
|
198
180
|
if (step.opType !== 'update') return undefined;
|
|
199
181
|
const items = step.diff;
|
|
200
182
|
if (items?.length !== 1) return undefined;
|
|
201
|
-
|
|
183
|
+
const newSchema = items[0].newSchema as { id?: Id } | undefined;
|
|
184
|
+
const oldSchema = items[0].oldSchema as { id?: Id } | undefined;
|
|
185
|
+
return newSchema?.id ?? oldSchema?.id ?? step.data?.id;
|
|
202
186
|
};
|
|
203
187
|
|
|
204
|
-
/** 解析
|
|
205
|
-
export const
|
|
188
|
+
/** 解析 step 中的目标可读名(用于 UI 展示)。 */
|
|
189
|
+
export const detectTargetName = (step: BaseStepValue): string | undefined => {
|
|
206
190
|
const items = step.diff;
|
|
207
191
|
if (step.opType === 'update') {
|
|
208
192
|
if (items?.length === 1) {
|
|
209
|
-
const node = items[0].newSchema || items[0].oldSchema;
|
|
193
|
+
const node = (items[0].newSchema || items[0].oldSchema) as { name?: string; type?: string; id?: Id } | undefined;
|
|
210
194
|
return (node?.name as string) || (node?.type as string) || (node?.id !== undefined ? `${node.id}` : undefined);
|
|
211
195
|
}
|
|
212
196
|
return items?.length ? `${items.length} 个节点` : undefined;
|
|
213
197
|
}
|
|
214
198
|
if (step.opType === 'add') {
|
|
215
199
|
if (items?.length === 1) {
|
|
216
|
-
const n = items[0].newSchema;
|
|
200
|
+
const n = items[0].newSchema as { name?: string; type?: string; id?: Id } | undefined;
|
|
217
201
|
return (n?.name as string) || (n?.type as string) || `${n?.id}`;
|
|
218
202
|
}
|
|
219
203
|
return items?.length ? `${items.length} 个节点` : undefined;
|
|
220
204
|
}
|
|
221
205
|
if (step.opType === 'remove') {
|
|
222
206
|
if (items?.length === 1) {
|
|
223
|
-
const n = items[0].oldSchema;
|
|
207
|
+
const n = items[0].oldSchema as { name?: string; type?: string; id?: Id } | undefined;
|
|
224
208
|
return (n?.name as string) || (n?.type as string) || `${n?.id}`;
|
|
225
209
|
}
|
|
226
210
|
return items?.length ? `${items.length} 个节点` : undefined;
|
|
@@ -290,10 +274,11 @@ export const getOrCreateStack = <T>(stacks: Record<Id, UndoRedo<T>>, id: Id): Un
|
|
|
290
274
|
};
|
|
291
275
|
|
|
292
276
|
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
277
|
+
* 撤销下限:当栈 index 0 是 `opType: 'initial'` 的基线 step 时为 1(基线不可被撤销),否则为 0。
|
|
278
|
+
* 适用于所有历史类型(page / codeBlock / dataSource / 扩展),把 cursor 钉在基线之上,
|
|
279
|
+
* 保证 undo / canUndo / goto 都不会越过初始基线。
|
|
295
280
|
*/
|
|
296
|
-
export const undoFloor = (undoRedo: UndoRedo<
|
|
281
|
+
export const undoFloor = (undoRedo: UndoRedo<any>): number => {
|
|
297
282
|
return undoRedo.getElementList()[0]?.opType === 'initial' ? 1 : 0;
|
|
298
283
|
};
|
|
299
284
|
|