feat(editor): 增强 *AndGetHistoryId 与 revertById 的 historyIds 能力

*AndGetHistoryId 同时返回原操作结果与 historyIds 数组;revertById/revertPageStepById 统一按 uuids 批量回滚。
This commit is contained in:
roymondchen 2026-06-12 16:32:47 +08:00
parent 27fac02e99
commit 6960bd50e1
12 changed files with 313 additions and 210 deletions

View File

@ -240,11 +240,12 @@
- **参数:** 同 [setCodeDslById](#setcodedslbyid)
- **返回:**
- {`Promise<string | null>`} 本次写入历史记录的 uuid未写入历史`doNotPushHistory: true` 等)时返回 `null`
- {`Promise<`[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)`>`}
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true` 等)时为 `[]`
- **详情:**
与 [setCodeDslById](#setcodedslbyid) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
与 [setCodeDslById](#setcodedslbyid) 行为完全一致,并在返回值中额外提供 `historyIds`,可用于精确引用 / 定位该条历史记录。
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
- **示例:**
@ -252,11 +253,11 @@
```js
import { codeBlockService } from "@tmagic/editor";
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", {
const { historyIds } = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", {
name: "代码块1",
content: "() => {}",
});
console.log(historyId); // 本次变更对应的历史记录 uuid或 null
console.log(historyIds); // 本次变更对应的历史记录 uuid 列表,或 []
```
## setCodeDslByIdSyncAndGetHistoryId
@ -264,44 +265,46 @@ console.log(historyId); // 本次变更对应的历史记录 uuid或 null
- **参数:** 同 [setCodeDslByIdSync](#setcodedslbyidsync)
- **返回:**
- {`string | null`} 本次写入历史记录的 uuid未写入历史`doNotPushHistory: true`、或 `force=false` 跳过等)时返回 `null`
- {[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true`、或 `force=false` 跳过等)时为 `[]`
- **详情:**
与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),仅把返回值换成本次写入历史记录的 `uuid`
与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),并在返回值中额外提供 `historyIds`
## deleteCodeDslByIdsAndGetHistoryId
- **参数:** 同 [deleteCodeDslByIds](#deletecodedslbyids)
- **返回:**
- {`Promise<string[]>`} 本次写入的全部历史记录 uuid按删除顺序未写入任何历史时返回空数组 `[]`
- {`Promise<`[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)`>`}
- `historyIds`:本次写入的全部历史记录 uuid按删除顺序未写入任何历史时为 `[]`
- **详情:**
与 [deleteCodeDslByIds](#deletecodedslbyids) 行为完全一致。由于一次可删除多个代码块、会产生多条历史记录,因此返回的是 uuid 数组(每条删除记录一个 uuid不存在的 id 不会入历史,也不会出现在返回数组中。
与 [deleteCodeDslByIds](#deletecodedslbyids) 行为完全一致。由于一次可删除多个代码块、会产生多条历史记录,因此使用 `historyIds` 数组(每条删除记录一个 uuid不存在的 id 不会入历史,也不会出现在 `historyIds` 中。
- **示例:**
```js
import { codeBlockService } from "@tmagic/editor";
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(["code_1", "code_2"]);
const { historyIds } = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(["code_1", "code_2"]);
console.log(historyIds); // ['xxxx', 'yyyy'],或 []
```
## revertById
- **参数:**
- `{string}` uuid 目标历史记录的 uuid通常由 [setCodeDslByIdAndGetHistoryId](#setcodedslbyidandgethistoryid) 等方法返回)
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 [setCodeDslByIdAndGetHistoryId](#setcodedslbyidandgethistoryid) 等方法返回`historyIds`
- **返回:**
- {`Promise<CodeBlockStepValue | null>`} 反向应用后产生的新 step找不到对应 uuid / 该步未应用时返回 `null`
- {`Promise<(`CodeBlockStepValue` | null)[]>`} 与入参同序的回滚结果列表,某项失败时为 `null`
- **详情:**
通过历史记录 uuid「回滚」某条代码块历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
仅无需调用方再传 `codeBlockId``index`:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚
通过历史记录 uuid「回滚」代码块历史步骤类 git revert 语义),语义同按 `(id, index)` 回滚,
仅无需调用方再传 `codeBlockId``index`。**按数组顺序依次回滚**
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
- **示例:**
@ -309,9 +312,9 @@ console.log(historyIds); // ['xxxx', 'yyyy'],或 []
```js
import { codeBlockService } from "@tmagic/editor";
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", { name: "代码块1" });
if (historyId) {
await codeBlockService.revertById(historyId);
const { historyIds } = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", { name: "代码块1" });
if (historyIds.length) {
await codeBlockService.revertById(historyIds);
}
```

View File

@ -411,11 +411,13 @@ dataSourceService.remove("ds_123");
- **参数:** 同 [add](#add)
- **返回:**
- {`string` | null} 本次写入历史记录的 uuid未写入历史`doNotPushHistory: true` 等)时返回 `null`
- {[`DslOpWithHistoryIdsResult<DataSourceSchema>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
- `result`:同 [add](#add) 的返回值(新增的数据源配置)
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true` 等)时为 `[]`
- **详情:**
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
与 [add](#add) 行为完全一致,并在返回值中额外提供 `historyIds`,可用于精确引用 / 定位该条历史记录。
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
- **示例:**
@ -423,12 +425,13 @@ dataSourceService.remove("ds_123");
```js
import { dataSourceService } from "@tmagic/editor";
const historyId = dataSourceService.addAndGetHistoryId({
const { result, historyIds } = dataSourceService.addAndGetHistoryId({
type: "http",
title: "用户信息",
url: "/api/user",
});
console.log(historyId); // 本次新增对应的历史记录 uuid或 null
console.log(result); // 新增的数据源配置
console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或 []
```
## updateAndGetHistoryId
@ -436,35 +439,38 @@ console.log(historyId); // 本次新增对应的历史记录 uuid或 null
- **参数:** 同 [update](#update)
- **返回:**
- {`string` | null} 本次写入历史记录的 uuid未写入历史时返回 `null`
- {[`DslOpWithHistoryIdsResult<DataSourceSchema>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
- `result`:同 [update](#update) 的返回值(更新后的数据源配置)
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史时为 `[]`
- **详情:**
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [update](#update) 行为完全一致,并在返回值中额外提供 `historyIds`
## removeAndGetHistoryId
- **参数:** 同 [remove](#remove)
- **返回:**
- {`string` | null} 本次写入历史记录的 uuid删除的 id 不存在或未写入历史时返回 `null`
- {[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
- `historyIds`:本次写入历史记录的 uuid 列表;删除的 id 不存在或未写入历史时为 `[]`
- **详情:**
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [remove](#remove) 行为完全一致,并在返回值中额外提供 `historyIds`
## revertById
- **参数:**
- `{string}` uuid 目标历史记录的 uuid通常由 [addAndGetHistoryId](#addandgethistoryid) 等方法返回)
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 [addAndGetHistoryId](#addandgethistoryid) 等方法返回`historyIds`
- **返回:**
- {`DataSourceStepValue` | null} 反向应用后产生的新 step找不到对应 uuid / 该步未应用时返回 `null`
- {(`DataSourceStepValue` | null)[]} 与入参同序的回滚结果列表,某项失败时为 `null`
- **详情:**
通过历史记录 uuid「回滚」某条数据源历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
仅无需调用方再传 `dataSourceId``index`:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚
通过历史记录 uuid「回滚」数据源历史步骤类 git revert 语义),语义同按 `(id, index)` 回滚,
仅无需调用方再传 `dataSourceId``index`。**按数组顺序依次回滚**
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
- **示例:**
@ -472,9 +478,9 @@ console.log(historyId); // 本次新增对应的历史记录 uuid或 null
```js
import { dataSourceService } from "@tmagic/editor";
const historyId = dataSourceService.addAndGetHistoryId({ type: "http", title: "用户信息" });
if (historyId) {
dataSourceService.revertById(historyId);
const { historyIds } = dataSourceService.addAndGetHistoryId({ type: "http", title: "用户信息" });
if (historyIds.length) {
dataSourceService.revertById(historyIds);
}
```

View File

@ -16,30 +16,32 @@
每条历史记录入栈时都会自动生成一个唯一标识 `uuid`(见 [StepValue](#undo)),可用于精确引用 / 定位某一条历史记录(如埋点、回滚、跨端同步等)。
DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(节点 / 节点集合 / void不会返回 `uuid`。若需要拿到本次写入历史记录的 `uuid`,可改用对应的 `*AndGetHistoryId` 方法:它们与原方法行为完全一致,仅把返回值换成本次写入历史记录的 `uuid``string`)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时返回 `null`
DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(节点 / 节点集合 / void不会返回 `uuid`。若需要同时拿到原操作结果与本次写入历史记录的 `uuid`,可改用对应的 `*AndGetHistoryId` 方法:它们与原方法行为完全一致,返回值类型为 [`DslOpWithHistoryIdsResult<T>`](#历史记录-uuid-与-andgethistoryid)`result` 为原方法返回值,`historyIds` 为本次写入的 uuid 列表)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时 `historyIds``[]`;单次操作通常返回含一个 uuid 的数组
| 原方法 | 取 uuid 的方法 | 返回值 |
| --- | --- | --- |
| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise<string \| null>` |
| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise<string \| null>` |
| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise<string \| null>` |
| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise<string \| null>` |
| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise<string \| null>` |
| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise<string \| null>` |
| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
[dataSourceService](./dataSourceServiceMethods.md) / [codeBlockService](./codeBlockServiceMethods.md) 也提供了同名约定的 `*AndGetHistoryId` 方法。
[dataSourceService](./dataSourceServiceMethods.md) / [codeBlockService](./codeBlockServiceMethods.md) 也提供了同名约定的 `*AndGetHistoryId` 方法,返回值约定相同(`result` + `historyIds`
拿到 `uuid` 后,可在需要时按 uuid「回滚」对应的历史记录类 git revert 语义,详见[历史记录面板](../../guide/advanced/history-list.md))。相比按 index 回滚uuid 不会随栈内步骤增删而变化,更适合业务侧持有引用后再回滚:
- 页面:[editorService.revertPageStepById(uuid)](#revertpagestepbyid)
- 数据源:[dataSourceService.revertById(uuid)](./dataSourceServiceMethods.md#revertbyid)
- 代码块:[codeBlockService.revertById(uuid)](./codeBlockServiceMethods.md#revertbyid)
- 页面:[editorService.revertPageStepById(uuids)](#revertpagestepbyid)
- 数据源:[dataSourceService.revertById(uuids)](./dataSourceServiceMethods.md#revertbyid)
- 代码块:[codeBlockService.revertById(uuids)](./codeBlockServiceMethods.md#revertbyid)
::: details 查看 HistoryOpOptions / DslOpOptions / HistoryOpSource 类型定义
::: details 查看 HistoryOpOptions / DslOpOptions / DslOpWithHistoryIdsResult / HistoryOpSource 类型定义
<<< @/../packages/editor/src/type.ts#HistoryOpOptions{ts}
<<< @/../packages/editor/src/type.ts#DslOpOptions{ts}
<<< @/../packages/editor/src/type.ts#DslOpWithHistoryIdsResult{ts}
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
:::
@ -738,23 +740,26 @@ alignCenter可以支持一次水平居中多个组件alignCenter是通过调
- **参数:** 同 [add](#add)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
- `result`:同 [add](#add) 的返回值(新增节点 / 节点数组)
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,见[历史记录 uuid 与 \*AndGetHistoryId](#历史记录-uuid-与-andgethistoryid)
与 [add](#add) 行为完全一致,并在返回值中额外提供 `historyIds`,见[历史记录 uuid 与 \*AndGetHistoryId](#历史记录-uuid-与-andgethistoryid)
- **示例:**
```js
import { editorService } from "@tmagic/editor";
const historyId = await editorService.addAndGetHistoryId(
const { result, historyIds } = await editorService.addAndGetHistoryId(
{ type: "text", text: "hello" },
parent,
{ historySource: "api" },
);
console.log(historyId); // 本次新增对应的历史记录 uuid或 null
console.log(result); // 新增节点
console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或 []
```
## removeAndGetHistoryId
@ -762,67 +767,74 @@ console.log(historyId); // 本次新增对应的历史记录 uuid或 null
- **参数:** 同 [remove](#remove)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [remove](#remove) 行为完全一致,并在返回值中额外提供 `historyIds`
## updateAndGetHistoryId
- **参数:** 同 [update](#update)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
- `result`:同 [update](#update) 的返回值(更新后的节点 / 节点数组)
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [update](#update) 行为完全一致,并在返回值中额外提供 `historyIds`
## moveLayerAndGetHistoryId
- **参数:** 同 [moveLayer](#movelayer)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [moveLayer](#movelayer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [moveLayer](#movelayer) 行为完全一致,并在返回值中额外提供 `historyIds`
## moveToContainerAndGetHistoryId
- **参数:** 同 [moveToContainer](#movetocontainer)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
- `result`:同 [moveToContainer](#movetocontainer) 的返回值(移动后的节点 / 节点数组)
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [moveToContainer](#movetocontainer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [moveToContainer](#movetocontainer) 行为完全一致,并在返回值中额外提供 `historyIds`
## dragToAndGetHistoryId
- **参数:** 同 [dragTo](#dragto)
- **返回:**
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
- **详情:**
与 [dragTo](#dragto) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
与 [dragTo](#dragto) 行为完全一致,并在返回值中额外提供 `historyIds`
## revertPageStepById
- **参数:**
- `{string}` uuid 目标历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid)(通常由 `*AndGetHistoryId` 方法返回)
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 `*AndGetHistoryId` 方法返回`historyIds`
- **返回:**
- {Promise<`StepValue` | null>} 反向应用后产生的新 step找不到对应 uuid / 该步未应用 / 反向失败时返回 `null`
- {Promise<(`StepValue` | null)[]>} 与入参同序的回滚结果列表,某项失败时为 `null`
- **详情:**
通过历史记录 uuid「回滚」当前页面的某条历史步骤(类 git revert 语义):不移动游标、不丢弃任何步骤,而是把目标 step 的修改**反向应用为一条全新的步骤**压入栈顶。语义与按 index 回滚一致,仅入参从 index 改为 uuid更适合业务侧持有引用后再回滚
通过历史记录 uuid「回滚」当前页面的历史步骤类 git revert 语义):不移动游标、不丢弃任何步骤,而是把目标 step 的修改**反向应用为一条全新的步骤**压入栈顶。**按数组顺序依次回滚**
::: tip
`opType: 'update'` 的步骤必须携带 `changeRecords` 才支持回滚(否则只能整节点替换,会冲掉后续无关变更);未应用(已被撤销)的步骤无法回滚。
@ -834,12 +846,10 @@ console.log(historyId); // 本次新增对应的历史记录 uuid或 null
import { editorService } from "@tmagic/editor";
// 执行操作时拿到本次历史记录 uuid
const historyId = await editorService.addAndGetHistoryId({ type: "text", text: "hello" });
const { historyIds } = await editorService.addAndGetHistoryId({ type: "text", text: "hello" });
// 之后任意时机按 uuid 回滚该步骤
if (historyId) {
await editorService.revertPageStepById(historyId);
}
// 之后任意时机按 uuid 回滚(支持单个或整批 historyIds
await editorService.revertPageStepById(historyIds);
```
## undo

View File

@ -61,11 +61,11 @@ const menu = ref({
- 数据源:`dataSourceService.revert(id, index)`
- 代码块:`codeBlockService.revert(id, index)`
如果业务侧在执行操作时已通过 `*AndGetHistoryId` 拿到了该条记录的 [uuid](/api/editor/editorServiceMethods.md#历史记录-uuid-与-andgethistoryid),也可以直接按 uuid 回滚(无需再关心 index / id且 uuid 不会随栈内步骤增删而变化):
如果业务侧在执行操作时已通过 `*AndGetHistoryId` 拿到了该条记录的 uuid[uuid 与 *AndGetHistoryId](/api/editor/editorServiceMethods.md#历史记录-uuid-与-andgethistoryid) 返回的 `historyIds`,也可以直接按 uuid 回滚(无需再关心 index / id且 uuid 不会随栈内步骤增删而变化):
- 页面:`editorService.revertPageStepById(uuid)`
- 数据源:`dataSourceService.revertById(uuid)`
- 代码块:`codeBlockService.revertById(uuid)`
- 页面:`editorService.revertPageStepById(uuids)`
- 数据源:`dataSourceService.revertById(uuids)`
- 代码块:`codeBlockService.revertById(uuids)`
### 4. 差异对比

View File

@ -32,13 +32,14 @@ import type {
AsyncHookPlugin,
CodeBlockStepValue,
CodeState,
DslOpWithHistoryIdsResult,
HistoryOpOptions,
HistoryOpOptionsWithChangeRecords,
} from '@editor/type';
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
import { getEditorConfig } from '@editor/utils/config';
import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
import { describeRevertStep } from '@editor/utils/history';
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
import BaseService from './BaseService';
@ -321,43 +322,46 @@ class CodeBlock extends BaseService {
// #region AndGetHistoryId
/**
* *AndGetHistoryId
* uuid{@link CodeBlockStepValue.uuid}
* {@link DslOpWithHistoryIdsResult} uuid
* / revert
*
* doNotPushHistory true null
* doNotPushHistory true historyIds `[]`
*/
/** 等价于 {@link setCodeDslById}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link setCodeDslById}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async setCodeDslByIdAndGetHistoryId(
id: Id,
codeConfig: Partial<CodeBlockContent>,
options: HistoryOpOptionsWithChangeRecords = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<void>> {
this.lastPushedHistoryId = null;
await this.setCodeDslById(id, codeConfig, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link setCodeDslByIdSync}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link setCodeDslByIdSync}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public setCodeDslByIdSyncAndGetHistoryId(
id: Id,
codeConfig: Partial<CodeBlockContent>,
force = true,
options: HistoryOpOptionsWithChangeRecords = {},
): string | null {
): DslOpWithHistoryIdsResult<void> {
this.lastPushedHistoryId = null;
this.setCodeDslByIdSync(id, codeConfig, force, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/**
* {@link deleteCodeDslByIds} uuid
*
* {@link deleteCodeDslByIds} uuid
* historyIds `[]`
*/
public async deleteCodeDslByIdsAndGetHistoryId(codeIds: Id[], options: HistoryOpOptions = {}): Promise<string[]> {
public async deleteCodeDslByIdsAndGetHistoryId(
codeIds: Id[],
options: HistoryOpOptions = {},
): Promise<DslOpWithHistoryIdsResult<void>> {
this.lastDeletedHistoryIds = [];
await this.deleteCodeDslByIds(codeIds, options);
return [...this.lastDeletedHistoryIds];
return { result: undefined, historyIds: [...this.lastDeletedHistoryIds] };
}
// #endregion AndGetHistoryId
@ -455,17 +459,19 @@ class CodeBlock extends BaseService {
}
/**
* uuid {@link revert}
* codeBlockId index uuid{@link CodeBlockStepValue.uuid}
*
* uuid {@link revert}
* codeBlockId index uuid
* `null`
*
* @param uuid uuid {@link setCodeDslByIdAndGetHistoryId}
* @returns step uuid / null
* @param uuids uuid {@link setCodeDslByIdAndGetHistoryId} `historyIds`
*/
public async revertById(uuid: string): Promise<CodeBlockStepValue | null> {
const location = historyService.findCodeBlockStepLocationByUuid(uuid);
if (!location) return null;
return await this.revert(location.id, location.index);
public async revertById(uuids: string[]): Promise<(CodeBlockStepValue | null)[]> {
const results: (CodeBlockStepValue | null)[] = [];
for (const uuid of uuids) {
const location = historyService.findCodeBlockStepLocationByUuid(uuid);
results.push(location ? await this.revert(location.id, location.index) : null);
}
return results;
}
/**

View File

@ -13,13 +13,14 @@ import storageService, { Protocol } from '@editor/services/storage';
import type {
DataSourceStepValue,
DatasourceTypeOption,
DslOpWithHistoryIdsResult,
HistoryOpOptions,
HistoryOpOptionsWithChangeRecords,
SyncHookPlugin,
} from '@editor/type';
import { getFormConfig, getFormValue } from '@editor/utils/data-source';
import { COPY_DS_STORAGE_KEY } from '@editor/utils/editor';
import { describeRevertStep } from '@editor/utils/history';
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
import BaseService from './BaseService';
@ -224,34 +225,37 @@ class DataSource extends BaseService {
// #region AndGetHistoryId
/**
* *AndGetHistoryId add / update / remove
* uuid{@link DataSourceStepValue.uuid}
* / revert
* {@link DslOpWithHistoryIdsResult} uuid {@link DataSourceStepValue.uuid}
* / revert
*
* doNotPushHistory true null
* doNotPushHistory true historyIds `[]`
*/
/** 等价于 {@link add},但返回本次写入历史记录的 uuid未入栈时返回 null。 */
public addAndGetHistoryId(config: DataSourceSchema, options: HistoryOpOptions = {}): string | null {
/** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public addAndGetHistoryId(
config: DataSourceSchema,
options: HistoryOpOptions = {},
): DslOpWithHistoryIdsResult<DataSourceSchema> {
this.lastPushedHistoryId = null;
this.add(config, options);
return this.lastPushedHistoryId;
const result = this.add(config, options);
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link update}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link update}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public updateAndGetHistoryId(
config: DataSourceSchema,
options: HistoryOpOptionsWithChangeRecords = {},
): string | null {
): DslOpWithHistoryIdsResult<DataSourceSchema> {
this.lastPushedHistoryId = null;
this.update(config, options);
return this.lastPushedHistoryId;
const result = this.update(config, options);
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link remove}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}): string | null {
/** 等价于 {@link remove}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}): DslOpWithHistoryIdsResult<void> {
this.lastPushedHistoryId = null;
this.remove(id, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
// #endregion AndGetHistoryId
@ -337,17 +341,18 @@ class DataSource extends BaseService {
}
/**
* uuid {@link revert}
* dataSourceId index uuid{@link DataSourceStepValue.uuid}
*
* uuid {@link revert}
* dataSourceId index uuid
* `null`
*
* @param uuid uuid {@link addAndGetHistoryId}
* @returns step uuid / null
* @param uuids uuid {@link addAndGetHistoryId} `historyIds`
*/
public revertById(uuid: string): DataSourceStepValue | null {
const location = historyService.findDataSourceStepLocationByUuid(uuid);
if (!location) return null;
return this.revert(location.id, location.index);
public revertById(uuids: string[]): (DataSourceStepValue | null)[] {
return uuids.map((uuid) => {
const location = historyService.findDataSourceStepLocationByUuid(uuid);
if (!location) return null;
return this.revert(location.id, location.index);
});
}
public createId(): string {

View File

@ -42,6 +42,7 @@ import type {
AsyncHookPlugin,
AsyncMethodName,
DslOpOptions,
DslOpWithHistoryIdsResult,
EditorEvents,
EditorNodeInfo,
HistoryOpSource,
@ -73,6 +74,7 @@ import {
setLayout,
toggleFixedPosition,
} from '@editor/utils/editor';
import { getLastPushedHistoryIds } from '@editor/utils/history';
import { beforePaste, getAddParent } from '@editor/utils/operator';
type MoveItem = { node: MNode; parent: MContainer; pageForOp: { name: string; id: Id } | null };
@ -1236,34 +1238,34 @@ class Editor extends BaseService {
// #region AndGetHistoryId
/**
* *AndGetHistoryId add / remove / update ...
* uuid{@link StepValue.uuid}
* / / revert
* {@link DslOpWithHistoryIdsResult} uuid {@link StepValue.uuid}
* / revert
*
* doNotPushHistory true / null
* doNotPushHistory true / historyIds `[]`
*/
/** 等价于 {@link add}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link add}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async addAndGetHistoryId(
addNode: AddMNode | MNode[],
parent?: MContainer | null,
options: DslOpOptions = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
this.lastPushedHistoryId = null;
await this.add(addNode, parent, options);
return this.lastPushedHistoryId;
const result = await this.add(addNode, parent, options);
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link remove}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link remove}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async removeAndGetHistoryId(
nodeOrNodeList: MNode | MNode[],
options: DslOpOptions = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<void>> {
this.lastPushedHistoryId = null;
await this.remove(nodeOrNodeList, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link update}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link update}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async updateAndGetHistoryId(
config: MNode | MNode[],
data: {
@ -1273,43 +1275,43 @@ class Editor extends BaseService {
historyDescription?: string;
historySource?: HistoryOpSource;
} = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
this.lastPushedHistoryId = null;
await this.update(config, data);
return this.lastPushedHistoryId;
const result = await this.update(config, data);
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link moveLayer}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link moveLayer}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async moveLayerAndGetHistoryId(
offset: number | LayerOffset,
options: DslOpOptions = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<void>> {
this.lastPushedHistoryId = null;
await this.moveLayer(offset, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link moveToContainer}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link moveToContainer}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async moveToContainerAndGetHistoryId(
config: MNode | MNode[],
targetId: Id,
options: DslOpOptions = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
this.lastPushedHistoryId = null;
await this.moveToContainer(config, targetId, options);
return this.lastPushedHistoryId;
const result = await this.moveToContainer(config, targetId, options);
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
/** 等价于 {@link dragTo}但返回本次写入历史记录的 uuid未入栈时返回 null)。 */
/** 等价于 {@link dragTo}并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
public async dragToAndGetHistoryId(
config: MNode | MNode[],
targetParent: MContainer,
targetIndex: number,
options: DslOpOptions = {},
): Promise<string | null> {
): Promise<DslOpWithHistoryIdsResult<void>> {
this.lastPushedHistoryId = null;
await this.dragTo(config, targetParent, targetIndex, options);
return this.lastPushedHistoryId;
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
}
// #endregion AndGetHistoryId
@ -1451,17 +1453,19 @@ class Editor extends BaseService {
}
/**
* uuid {@link revertPageStep}
* index uuid{@link StepValue.uuid}uuid
*
* uuid {@link revertPageStep}
* index uuid {@link StepValue.uuid}
* `null`
*
* @param uuid uuid *AndGetHistoryId
* @returns step uuid / / null
* @param uuids uuid *AndGetHistoryId `historyIds`
*/
public async revertPageStepById(uuid: string): Promise<StepValue | null> {
const index = historyService.getPageStepIndexByUuid(uuid);
if (index < 0) return null;
return this.revertPageStep(index);
public async revertPageStepById(uuids: string[]): Promise<(StepValue | null)[]> {
const results: (StepValue | null)[] = [];
for (const uuid of uuids) {
const index = historyService.getPageStepIndexByUuid(uuid);
results.push(index < 0 ? null : await this.revertPageStep(index));
}
return results;
}
/**

View File

@ -737,6 +737,14 @@ export type HistoryOpSource =
| (string & {});
// #endregion HistoryOpSource
// #region DslOpWithHistoryIdsResult
/** *AndGetHistoryId 系列方法返回值:原操作结果 + 本次写入历史记录的 uuid 列表(未入栈时为 `[]`)。 */
export type DslOpWithHistoryIdsResult<T> = {
result: T;
historyIds: string[];
};
// #endregion DslOpWithHistoryIdsResult
// #region StepDiffItem
/**
* diff / /

View File

@ -276,3 +276,6 @@ export const getOrCreateStack = <T>(stacks: Record<Id, UndoRedo<T>>, id: Id): Un
export const undoFloor = (undoRedo: UndoRedo<StepValue>): number => {
return undoRedo.getElementList()[0]?.opType === 'initial' ? 1 : 0;
};
/** 将单次 push 产生的 history uuid或 null转为 *AndGetHistoryId 返回用的 uuid 列表。 */
export const getLastPushedHistoryIds = (historyId: string | null): string[] => (historyId ? [historyId] : []);

View File

@ -240,39 +240,39 @@ describe('CodeBlockService - *AndGetHistoryId', () => {
test('setCodeDslByIdSyncAndGetHistoryId 返回本次写入历史记录的 uuid', async () => {
await codeBlockService.setCodeDsl({} as any);
const historyId = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid('a'));
const { historyIds } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid('a'));
// 与默认行为一致:内容仍被写入
expect(codeBlockService.getCodeContentById('a')?.name).toBe('A');
});
test('setCodeDslByIdSyncAndGetHistoryId - force=false 已存在时返回 null', async () => {
test('setCodeDslByIdSyncAndGetHistoryId - force=false 已存在时返回空数组', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'A' } } as any);
const historyId = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'NEW' } as any, false);
expect(historyId).toBeNull();
const { historyIds } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'NEW' } as any, false);
expect(historyIds).toEqual([]);
});
test('setCodeDslByIdSyncAndGetHistoryId - doNotPushHistory 时返回 null', async () => {
test('setCodeDslByIdSyncAndGetHistoryId - doNotPushHistory 时返回空数组', async () => {
await codeBlockService.setCodeDsl({} as any);
const historyId = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any, true, {
const { historyIds } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any, true, {
doNotPushHistory: true,
});
expect(historyId).toBeNull();
expect(historyIds).toEqual([]);
});
test('setCodeDslByIdAndGetHistoryIdasync返回本次写入历史记录的 uuid', async () => {
await codeBlockService.setCodeDsl({} as any);
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId('a', { name: 'A' } as any);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid('a'));
const { historyIds } = await codeBlockService.setCodeDslByIdAndGetHistoryId('a', { name: 'A' } as any);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid('a'));
});
test('deleteCodeDslByIdsAndGetHistoryId 返回每条删除记录的 uuid 数组', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'A' }, b: { name: 'B' } } as any);
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['a', 'b']);
const { historyIds } = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['a', 'b']);
expect(Array.isArray(historyIds)).toBe(true);
expect(historyIds).toHaveLength(2);
expect(historyIds[0]).toBe(lastStepUuid('a'));
@ -282,14 +282,14 @@ describe('CodeBlockService - *AndGetHistoryId', () => {
test('deleteCodeDslByIdsAndGetHistoryId - 不存在的 id 不计入返回数组', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'A' } } as any);
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['a', 'ghost']);
const { historyIds } = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['a', 'ghost']);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid('a'));
});
test('deleteCodeDslByIdsAndGetHistoryId - 全部不存在时返回空数组', async () => {
await codeBlockService.setCodeDsl({} as any);
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['ghost']);
const { historyIds } = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(['ghost']);
expect(historyIds).toEqual([]);
});
});
@ -297,20 +297,21 @@ describe('CodeBlockService - *AndGetHistoryId', () => {
describe('CodeBlockService - revertById', () => {
test('通过 uuid 回滚新增(删除代码块内容)', async () => {
await codeBlockService.setCodeDsl({} as any);
const uuid = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
const { historyIds } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
const uuid = historyIds[0];
expect(typeof uuid).toBe('string');
expect(codeBlockService.getCodeContentById('a')?.name).toBe('A');
const reverted = await codeBlockService.revertById(uuid!);
expect(reverted).not.toBeNull();
const reverted = await codeBlockService.revertById([uuid!]);
expect(reverted[0]).not.toBeNull();
expect(codeBlockService.getCodeContentById('a')).toBeNull();
});
test('按 uuid 能定位到对应 (id, index)', async () => {
await codeBlockService.setCodeDsl({} as any);
const uuid = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
const { historyIds } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
const location = historyService.findCodeBlockStepLocationByUuid(uuid!);
const location = historyService.findCodeBlockStepLocationByUuid(historyIds[0]!);
expect(location).toEqual({ id: 'a', index: 0 });
});
@ -318,8 +319,21 @@ describe('CodeBlockService - revertById', () => {
await codeBlockService.setCodeDsl({} as any);
codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
await expect(codeBlockService.revertById('not-exist')).resolves.toBeNull();
await expect(codeBlockService.revertById('')).resolves.toBeNull();
await expect(codeBlockService.revertById(['not-exist'])).resolves.toEqual([null]);
await expect(codeBlockService.revertById([''])).resolves.toEqual([null]);
});
test('支持传入 uuid 数组并按顺序回滚', async () => {
await codeBlockService.setCodeDsl({} as any);
const { historyIds: ids1 } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('a', { name: 'A' } as any);
const { historyIds: ids2 } = codeBlockService.setCodeDslByIdSyncAndGetHistoryId('b', { name: 'B' } as any);
const reverted = await codeBlockService.revertById([ids1[0]!, ids2[0]!]);
expect(reverted).toHaveLength(2);
expect(reverted[0]).not.toBeNull();
expect(reverted[1]).not.toBeNull();
expect(codeBlockService.getCodeContentById('a')).toBeNull();
expect(codeBlockService.getCodeContentById('b')).toBeNull();
});
});

View File

@ -197,39 +197,41 @@ describe('DataSource service - *AndGetHistoryId', () => {
const ds = dataSource.add({ id: 'temp', title: 'a', type: 'base' } as any);
historyService.reset();
const historyId = dataSource.addAndGetHistoryId({ id: 'ds_new', title: 'a', type: 'base' } as any);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid('ds_new'));
const { result, historyIds } = dataSource.addAndGetHistoryId({ id: 'ds_new', title: 'a', type: 'base' } as any);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid('ds_new'));
expect(result.id).toBe('ds_new');
// 与默认 add 行为一致:仍会写入数据源
expect(dataSource.getDataSourceById('ds_new')).toBeDefined();
expect(ds).toBeDefined();
});
test('addAndGetHistoryId 传 doNotPushHistory 时返回 null', () => {
const historyId = dataSource.addAndGetHistoryId({ id: 'ds_x', title: 'a', type: 'base' } as any, {
test('addAndGetHistoryId 传 doNotPushHistory 时返回空数组', () => {
const { historyIds } = dataSource.addAndGetHistoryId({ id: 'ds_x', title: 'a', type: 'base' } as any, {
doNotPushHistory: true,
});
expect(historyId).toBeNull();
expect(historyIds).toEqual([]);
});
test('updateAndGetHistoryId 返回本次写入历史记录的 uuid', () => {
const created = dataSource.add({ title: 'a', type: 'base' } as any);
historyService.reset();
const historyId = dataSource.updateAndGetHistoryId({ ...created, title: 'b' } as any);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid(created.id!));
const { result, historyIds } = dataSource.updateAndGetHistoryId({ ...created, title: 'b' } as any);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid(created.id!));
expect(result.title).toBe('b');
});
test('removeAndGetHistoryId 返回本次写入历史记录的 uuid不存在的 id 返回 null', () => {
test('removeAndGetHistoryId 返回本次写入历史记录的 uuid不存在的 id 返回空数组', () => {
const created = dataSource.add({ title: 'a', type: 'base' } as any);
historyService.reset();
const historyId = dataSource.removeAndGetHistoryId(created.id!);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid(created.id!));
const { historyIds } = dataSource.removeAndGetHistoryId(created.id!);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid(created.id!));
expect(dataSource.removeAndGetHistoryId('ghost')).toBeNull();
expect(dataSource.removeAndGetHistoryId('ghost').historyIds).toEqual([]);
});
});
@ -240,8 +242,8 @@ describe('DataSource service - revertById', () => {
expect(typeof uuid).toBe('string');
expect(dataSource.getDataSourceById(created.id!)).toBeDefined();
const reverted = dataSource.revertById(uuid!);
expect(reverted).not.toBeNull();
const reverted = dataSource.revertById([uuid!]);
expect(reverted[0]).not.toBeNull();
expect(dataSource.getDataSourceById(created.id!)).toBeUndefined();
});
@ -255,8 +257,22 @@ describe('DataSource service - revertById', () => {
test('找不到 uuid 时返回 null', () => {
dataSource.add({ title: 'a', type: 'base' } as any);
expect(dataSource.revertById('not-exist')).toBeNull();
expect(dataSource.revertById('')).toBeNull();
expect(dataSource.revertById(['not-exist'])).toEqual([null]);
expect(dataSource.revertById([''])).toEqual([null]);
});
test('支持传入 uuid 数组并按顺序回滚', () => {
const ds1 = dataSource.add({ title: 'a', type: 'base' } as any);
const ds2 = dataSource.add({ title: 'b', type: 'base' } as any);
const uuid1 = historyService.getDataSourceStepList(ds1.id!).slice(-1)[0]?.step.uuid;
const uuid2 = historyService.getDataSourceStepList(ds2.id!).slice(-1)[0]?.step.uuid;
const reverted = dataSource.revertById([uuid1!, uuid2!]);
expect(reverted).toHaveLength(2);
expect(reverted[0]).not.toBeNull();
expect(reverted[1]).not.toBeNull();
expect(dataSource.getDataSourceById(ds1.id!)).toBeUndefined();
expect(dataSource.getDataSourceById(ds2.id!)).toBeUndefined();
});
});

View File

@ -834,19 +834,20 @@ describe('*AndGetHistoryId', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const historyId = await editorService.addAndGetHistoryId({ type: 'text' });
expect(typeof historyId).toBe('string');
expect(historyId).toBeTruthy();
expect(historyId).toBe(lastStepUuid());
const { result, historyIds } = await editorService.addAndGetHistoryId({ type: 'text' });
expect(result).toBeTruthy();
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBeTruthy();
expect(historyIds[0]).toBe(lastStepUuid());
});
test('addAndGetHistoryId 传 doNotPushHistory 时返回 null', async () => {
test('addAndGetHistoryId 传 doNotPushHistory 时返回空数组', async () => {
editorService.set('root', cloneDeep(root));
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const historyId = await editorService.addAndGetHistoryId({ type: 'text' }, null, { doNotPushHistory: true });
expect(historyId).toBeNull();
const { historyIds } = await editorService.addAndGetHistoryId({ type: 'text' }, null, { doNotPushHistory: true });
expect(historyIds).toEqual([]);
});
test('updateAndGetHistoryId 返回本次写入历史记录的 uuid', async () => {
@ -854,9 +855,14 @@ describe('*AndGetHistoryId', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const historyId = await editorService.updateAndGetHistoryId({ id: NodeId.NODE_ID, type: 'text', text: 'x' });
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid());
const { result, historyIds } = await editorService.updateAndGetHistoryId({
id: NodeId.NODE_ID,
type: 'text',
text: 'x',
});
expect(result).toBeTruthy();
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid());
});
test('removeAndGetHistoryId 返回本次写入历史记录的 uuid', async () => {
@ -864,9 +870,9 @@ describe('*AndGetHistoryId', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const historyId = await editorService.removeAndGetHistoryId({ id: NodeId.NODE_ID, type: 'text' });
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid());
const { historyIds } = await editorService.removeAndGetHistoryId({ id: NodeId.NODE_ID, type: 'text' });
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid());
});
test('moveLayerAndGetHistoryId 返回本次写入历史记录的 uuid', async () => {
@ -874,9 +880,9 @@ describe('*AndGetHistoryId', () => {
historyService.reset();
await editorService.select(NodeId.NODE_ID);
const historyId = await editorService.moveLayerAndGetHistoryId(1);
expect(typeof historyId).toBe('string');
expect(historyId).toBe(lastStepUuid());
const { historyIds } = await editorService.moveLayerAndGetHistoryId(1);
expect(historyIds).toHaveLength(1);
expect(historyIds[0]).toBe(lastStepUuid());
});
});
@ -886,15 +892,16 @@ describe('revertPageStepById', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const uuid = await editorService.addAndGetHistoryId({ type: 'text' });
const { historyIds } = await editorService.addAndGetHistoryId({ type: 'text' });
const uuid = historyIds[0];
expect(typeof uuid).toBe('string');
const addedStep = historyService.getPageStepList().find((e) => e.step.uuid === uuid)!.step;
const addedId = addedStep.diff[0].newSchema!.id;
expect(editorService.getNodeById(addedId)).toBeTruthy();
const reverted = await editorService.revertPageStepById(uuid!);
expect(reverted).not.toBeNull();
const reverted = await editorService.revertPageStepById([uuid!]);
expect(reverted[0]).not.toBeNull();
// 回滚git revert 语义)会把被新增的节点删掉
expect(editorService.getNodeById(addedId)).toBeNull();
});
@ -904,7 +911,8 @@ describe('revertPageStepById', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const uuid = await editorService.addAndGetHistoryId({ type: 'text' });
const { historyIds } = await editorService.addAndGetHistoryId({ type: 'text' });
const uuid = historyIds[0];
const index = historyService.getPageStepIndexByUuid(uuid!);
expect(index).toBeGreaterThanOrEqual(0);
});
@ -914,7 +922,27 @@ describe('revertPageStepById', () => {
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
expect(await editorService.revertPageStepById('not-exist')).toBeNull();
expect(await editorService.revertPageStepById('')).toBeNull();
expect(await editorService.revertPageStepById(['not-exist'])).toEqual([null]);
expect(await editorService.revertPageStepById([''])).toEqual([null]);
});
test('支持传入 uuid 数组并按顺序回滚', async () => {
editorService.set('root', cloneDeep(root));
historyService.reset();
await editorService.select(NodeId.PAGE_ID);
const { historyIds: ids1 } = await editorService.addAndGetHistoryId({ type: 'text' });
const { historyIds: ids2 } = await editorService.addAndGetHistoryId({ type: 'text' });
const uuids = [ids1[0]!, ids2[0]!];
const addedId1 = historyService.getPageStepList().find((e) => e.step.uuid === uuids[0])!.step.diff[0].newSchema!.id;
const addedId2 = historyService.getPageStepList().find((e) => e.step.uuid === uuids[1])!.step.diff[0].newSchema!.id;
const reverted = await editorService.revertPageStepById(uuids);
expect(reverted).toHaveLength(2);
expect(reverted[0]).not.toBeNull();
expect(reverted[1]).not.toBeNull();
expect(editorService.getNodeById(addedId1)).toBeNull();
expect(editorService.getNodeById(addedId2)).toBeNull();
});
});