diff --git a/docs/api/editor/codeBlockServiceMethods.md b/docs/api/editor/codeBlockServiceMethods.md index 1aa4235a..292b06a9 100644 --- a/docs/api/editor/codeBlockServiceMethods.md +++ b/docs/api/editor/codeBlockServiceMethods.md @@ -240,11 +240,12 @@ - **参数:** 同 [setCodeDslById](#setcodedslbyid) - **返回:** - - {`Promise`} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null` + - {`Promise<`[`DslOpWithHistoryIdsResult`](./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`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)} + - `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true`、或 `force=false` 跳过等)时为 `[]` - **详情:** - 与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),仅把返回值换成本次写入历史记录的 `uuid` + 与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),并在返回值中额外提供 `historyIds` ## deleteCodeDslByIdsAndGetHistoryId - **参数:** 同 [deleteCodeDslByIds](#deletecodedslbyids) - **返回:** - - {`Promise`} 本次写入的全部历史记录 uuid(按删除顺序);未写入任何历史时返回空数组 `[]` + - {`Promise<`[`DslOpWithHistoryIdsResult`](./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`} 反向应用后产生的新 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); } ``` diff --git a/docs/api/editor/dataSourceServiceMethods.md b/docs/api/editor/dataSourceServiceMethods.md index 6f52075d..df86dfa2 100644 --- a/docs/api/editor/dataSourceServiceMethods.md +++ b/docs/api/editor/dataSourceServiceMethods.md @@ -411,11 +411,13 @@ dataSourceService.remove("ds_123"); - **参数:** 同 [add](#add) - **返回:** - - {`string` | null} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null` + - {[`DslOpWithHistoryIdsResult`](./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`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)} + - `result`:同 [update](#update) 的返回值(更新后的数据源配置) + - `historyIds`:本次写入历史记录的 uuid 列表;未写入历史时为 `[]` - **详情:** - 与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid` + 与 [update](#update) 行为完全一致,并在返回值中额外提供 `historyIds` ## removeAndGetHistoryId - **参数:** 同 [remove](#remove) - **返回:** - - {`string` | null} 本次写入历史记录的 uuid;删除的 id 不存在或未写入历史时返回 `null` + - {[`DslOpWithHistoryIdsResult`](./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); } ``` diff --git a/docs/api/editor/editorServiceMethods.md b/docs/api/editor/editorServiceMethods.md index 9fd8e2a6..401f6df0 100644 --- a/docs/api/editor/editorServiceMethods.md +++ b/docs/api/editor/editorServiceMethods.md @@ -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`](#历史记录-uuid-与-andgethistoryid)(`result` 为原方法返回值,`historyIds` 为本次写入的 uuid 列表)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时 `historyIds` 为 `[]`;单次操作通常返回含一个 uuid 的数组。 | 原方法 | 取 uuid 的方法 | 返回值 | | --- | --- | --- | -| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise` | -| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise` | -| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise` | -| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise` | -| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise` | -| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise` | +| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise>` | +| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise>` | +| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise>` | +| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise>` | +| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise>` | +| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise>` | -[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`](#历史记录-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`](#历史记录-uuid-与-andgethistoryid)>} + - `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]` - **详情:** - 与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid` + 与 [remove](#remove) 行为完全一致,并在返回值中额外提供 `historyIds` ## updateAndGetHistoryId - **参数:** 同 [update](#update) - **返回:** - - {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null` + - {Promise<[`DslOpWithHistoryIdsResult`](#历史记录-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`](#历史记录-uuid-与-andgethistoryid)>} + - `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]` - **详情:** - 与 [moveLayer](#movelayer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid` + 与 [moveLayer](#movelayer) 行为完全一致,并在返回值中额外提供 `historyIds` ## moveToContainerAndGetHistoryId - **参数:** 同 [moveToContainer](#movetocontainer) - **返回:** - - {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null` + - {Promise<[`DslOpWithHistoryIdsResult`](#历史记录-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`](#历史记录-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 diff --git a/docs/guide/advanced/history-list.md b/docs/guide/advanced/history-list.md index 815b9919..c263850e 100644 --- a/docs/guide/advanced/history-list.md +++ b/docs/guide/advanced/history-list.md @@ -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. 差异对比 diff --git a/packages/editor/src/services/codeBlock.ts b/packages/editor/src/services/codeBlock.ts index 9c7a686f..2ac18af8 100644 --- a/packages/editor/src/services/codeBlock.ts +++ b/packages/editor/src/services/codeBlock.ts @@ -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, options: HistoryOpOptionsWithChangeRecords = {}, - ): Promise { + ): Promise> { 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, force = true, options: HistoryOpOptionsWithChangeRecords = {}, - ): string | null { + ): DslOpWithHistoryIdsResult { 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 { + public async deleteCodeDslByIdsAndGetHistoryId( + codeIds: Id[], + options: HistoryOpOptions = {}, + ): Promise> { 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 { - 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; } /** diff --git a/packages/editor/src/services/dataSource.ts b/packages/editor/src/services/dataSource.ts index 0d94ae84..a8ce89da 100644 --- a/packages/editor/src/services/dataSource.ts +++ b/packages/editor/src/services/dataSource.ts @@ -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 { 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 { 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 { 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 { diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 0c2478b3..5750a65c 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -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 { + ): Promise> { 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 { + ): Promise> { 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 { + ): Promise> { 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 { + ): Promise> { 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 { + ): Promise> { 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 { + ): Promise> { 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 { - 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; } /** diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 48c7786a..60d303c6 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -737,6 +737,14 @@ export type HistoryOpSource = | (string & {}); // #endregion HistoryOpSource +// #region DslOpWithHistoryIdsResult +/** *AndGetHistoryId 系列方法返回值:原操作结果 + 本次写入历史记录的 uuid 列表(未入栈时为 `[]`)。 */ +export type DslOpWithHistoryIdsResult = { + result: T; + historyIds: string[]; +}; +// #endregion DslOpWithHistoryIdsResult + // #region StepDiffItem /** * 单条变更的 diff 描述,统一表达「页面节点 / 代码块 / 数据源」的变化内容, diff --git a/packages/editor/src/utils/history.ts b/packages/editor/src/utils/history.ts index 86aabd30..90137df9 100644 --- a/packages/editor/src/utils/history.ts +++ b/packages/editor/src/utils/history.ts @@ -276,3 +276,6 @@ export const getOrCreateStack = (stacks: Record>, id: Id): Un export const undoFloor = (undoRedo: UndoRedo): number => { return undoRedo.getElementList()[0]?.opType === 'initial' ? 1 : 0; }; + +/** 将单次 push 产生的 history uuid(或 null)转为 *AndGetHistoryId 返回用的 uuid 列表。 */ +export const getLastPushedHistoryIds = (historyId: string | null): string[] => (historyId ? [historyId] : []); diff --git a/packages/editor/tests/unit/services/codeBlock.spec.ts b/packages/editor/tests/unit/services/codeBlock.spec.ts index 89550ec2..67444a2c 100644 --- a/packages/editor/tests/unit/services/codeBlock.spec.ts +++ b/packages/editor/tests/unit/services/codeBlock.spec.ts @@ -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('setCodeDslByIdAndGetHistoryId(async)返回本次写入历史记录的 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(); }); }); diff --git a/packages/editor/tests/unit/services/dataSource.spec.ts b/packages/editor/tests/unit/services/dataSource.spec.ts index 1244334c..7ae504d1 100644 --- a/packages/editor/tests/unit/services/dataSource.spec.ts +++ b/packages/editor/tests/unit/services/dataSource.spec.ts @@ -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(); }); }); diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index 3bf6f0e2..f050fbc8 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -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(); }); });