mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-23 00:43:38 +00:00
feat(editor): 新增 DSL 修改方法的 doNotSwitchPage 选项
在 add / remove / doRemove / sort / paste / alignCenter / moveToContainer 的 options 对象中新增 doNotSwitchPage,与 doNotSelect 合并为同一配置 DslOpOptions, 用于在 DSL 操作(新增 / 删除 / 跨页移动)会引发当前页面切换时跳过该次切换。 - 抽取共用类型 DslOpOptions 到 type.ts 并对外导出 - 新增 editorService.isOnDifferentPage 辅助方法用于跨页判断 - 修复 doUpdate 同步 state.page 时无条件覆盖的问题:只在被更新页就是当前页时才同步引用,避免「更新非当前页」误把编辑器切到该页 - doRemove 中对已删除节点的引用清理与当前页清空逻辑提升为无条件执行,避免 doNotSelect / doNotSwitchPage 跳过后续 select 时 state 持有已删除节点 - 补充对应单元测试与 API 文档 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
eb1c5a3ec1
commit
3d038513e3
@ -170,6 +170,29 @@ const parent = editorService.getParentById("text_123");
|
||||
console.log(parent);
|
||||
```
|
||||
|
||||
## isOnDifferentPage
|
||||
|
||||
- **参数:**
|
||||
- {`MNode`} node 节点配置
|
||||
|
||||
- **返回:**
|
||||
- `{boolean}` true 表示该节点位于非当前页面(即选中该节点将会引起当前页面切换)
|
||||
|
||||
- **详情:**
|
||||
|
||||
判断给定节点是否位于非当前页面,通常用于配合 `doNotSwitchPage` 选项判断 DSL 操作是否会引起页面切换
|
||||
|
||||
- **示例:**
|
||||
|
||||
```js
|
||||
import { editorService } from "@tmagic/editor";
|
||||
|
||||
const otherPageNode = editorService.getNodeById("text_456");
|
||||
if (editorService.isOnDifferentPage(otherPageNode)) {
|
||||
console.log("该节点在其它页面,操作会触发页面切换");
|
||||
}
|
||||
```
|
||||
|
||||
## getLayout
|
||||
|
||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||
@ -334,6 +357,7 @@ editorService.highlight("text_123");
|
||||
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
||||
- `{boolean}` doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
||||
|
||||
- **返回:**
|
||||
- {Promise<`MNode` | `MNode`[]>} 新增的组件或组件集合
|
||||
@ -357,6 +381,7 @@ editorService.highlight("text_123");
|
||||
- {`MNode`} node 要删除的节点
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 删除后是否不更新当前选中节点(默认 false)
|
||||
- `{boolean}` doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||
|
||||
- **返回:**
|
||||
- `{Promise<void>}`
|
||||
@ -365,6 +390,10 @@ editorService.highlight("text_123");
|
||||
|
||||
删除指定的组件或者页面
|
||||
|
||||
:::tip
|
||||
无论是否传入 `doNotSelect` / `doNotSwitchPage`,当被删除节点在当前选中列表中时,state 都会自动移除该节点的引用;当被删除的正好是当前页面时,state.page 也会同步清空,避免持有已删除节点
|
||||
:::
|
||||
|
||||
## remove
|
||||
|
||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||
@ -373,6 +402,7 @@ editorService.highlight("text_123");
|
||||
- {`MNode` | `MNode`[])} node 要删除的节点或节点集合
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面)
|
||||
- `{boolean}` doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||
|
||||
- **返回:**
|
||||
- `{Promise<void>}`
|
||||
@ -413,6 +443,8 @@ editorService.highlight("text_123");
|
||||
节点中应该要有id,不然不知道要更新哪个节点
|
||||
|
||||
当被更新节点正好在当前选中列表中时,state 会自动同步到新的节点引用,无需调用方处理
|
||||
|
||||
当被更新节点正好是当前页面时,state.page 也会同步到新的节点引用;更新非当前页面(不同 ID)时不会把编辑器切到该页
|
||||
:::
|
||||
|
||||
## update
|
||||
@ -448,6 +480,7 @@ editorService.highlight("text_123");
|
||||
- `{ string | number }` id2
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 排序后是否不更新当前选中节点(默认 false)
|
||||
- `{boolean}` doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
|
||||
- **返回:**
|
||||
- `{Promise<void>}`
|
||||
@ -514,6 +547,7 @@ editorService.highlight("text_123");
|
||||
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 粘贴后是否不更新当前选中节点(默认 false)
|
||||
- `{boolean}` doNotSwitchPage 粘贴后是否不切换当前页面(默认 false;跨页粘贴时为 true 会跳过页面切换)
|
||||
|
||||
- **返回:**
|
||||
- {Promise<`MNode` | `MNode`[]>} 添加后的组件节点配置
|
||||
@ -550,6 +584,7 @@ editorService.highlight("text_123");
|
||||
- {`MNode` | `MNode`[]} config 需要居中的组件或者组件集合
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 居中后是否不更新当前选中节点(默认 false)
|
||||
- `{boolean}` doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
|
||||
- **返回:**
|
||||
- {Promise<`MNode` | `MNode`[]>}
|
||||
@ -589,6 +624,7 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
||||
- `{string | number}` targetId 容器ID
|
||||
- `{Object}` options 可选配置
|
||||
- `{boolean}` doNotSelect 移动后是否不更新当前选中节点(默认 false)
|
||||
- `{boolean}` doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
|
||||
|
||||
- **返回:**
|
||||
- Promise<`MNode` | undefined>
|
||||
|
||||
@ -33,6 +33,7 @@ import type {
|
||||
AddMNode,
|
||||
AsyncHookPlugin,
|
||||
AsyncMethodName,
|
||||
DslOpOptions,
|
||||
EditorEvents,
|
||||
EditorNodeInfo,
|
||||
HistoryOpType,
|
||||
@ -191,6 +192,22 @@ class Editor extends BaseService {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定节点是否位于非当前页面(即选中该节点将会引起当前页面切换)
|
||||
* @param node 节点
|
||||
* @returns true 表示该节点位于非当前页面
|
||||
*/
|
||||
public isOnDifferentPage(node: MNode): boolean {
|
||||
const currentPageId = this.get('page')?.id;
|
||||
if (currentPageId === undefined || currentPageId === null) return false;
|
||||
if (isPage(node) || isPageFragment(node)) {
|
||||
return `${node.id}` !== `${currentPageId}`;
|
||||
}
|
||||
const nodePage = this.getNodeInfo(node.id, false).page;
|
||||
if (!nodePage) return false;
|
||||
return `${nodePage.id}` !== `${currentPageId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有容器拥有布局
|
||||
*/
|
||||
@ -370,15 +387,16 @@ class Editor extends BaseService {
|
||||
* @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
||||
* @param options.doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
||||
* @returns 添加后的节点
|
||||
*/
|
||||
public async add(
|
||||
addNode: AddMNode | MNode[],
|
||||
parent?: MContainer | null,
|
||||
options?: { doNotSelect?: boolean },
|
||||
options?: DslOpOptions,
|
||||
): Promise<MNode | MNode[]> {
|
||||
const safeParentNode = safeParent(parent);
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
@ -409,14 +427,19 @@ class Editor extends BaseService {
|
||||
);
|
||||
|
||||
if (newNodes.length > 1) {
|
||||
if (!doNotSelect) {
|
||||
// 多选时只要任一新增节点位于非当前页面,触发的 multiSelect 就会引起页面切换
|
||||
const wouldSwitchPage = newNodes.some((n) => this.isOnDifferentPage(n));
|
||||
if (!doNotSelect && !(doNotSwitchPage && wouldSwitchPage)) {
|
||||
const newNodeIds = newNodes.map((node) => node.id);
|
||||
// 触发选中样式
|
||||
stage?.multiSelect(newNodeIds);
|
||||
await this.multiSelect(newNodeIds);
|
||||
}
|
||||
} else {
|
||||
if (!doNotSelect) {
|
||||
const wouldSwitchPage = this.isOnDifferentPage(newNodes[0]);
|
||||
const skipSelect = doNotSelect || (doNotSwitchPage && wouldSwitchPage);
|
||||
|
||||
if (!skipSelect) {
|
||||
await this.select(newNodes[0]);
|
||||
}
|
||||
|
||||
@ -424,7 +447,7 @@ class Editor extends BaseService {
|
||||
this.state.pageLength += 1;
|
||||
} else if (isPageFragment(newNodes[0])) {
|
||||
this.state.pageFragmentLength += 1;
|
||||
} else if (!doNotSelect) {
|
||||
} else if (!skipSelect) {
|
||||
// 新增页面,这个时候页面还有渲染出来,此时select会出错,在runtime-ready的时候回去select
|
||||
stage?.select(newNodes[0].id);
|
||||
}
|
||||
@ -453,8 +476,8 @@ class Editor extends BaseService {
|
||||
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
||||
}
|
||||
|
||||
public async doRemove(node: MNode, options?: { doNotSelect?: boolean }): Promise<void> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
public async doRemove(node: MNode, options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
const root = this.get('root');
|
||||
if (!root) throw new Error('root不能为空');
|
||||
@ -471,21 +494,19 @@ class Editor extends BaseService {
|
||||
const stage = this.get('stage');
|
||||
stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
|
||||
|
||||
if (doNotSelect) {
|
||||
// 当被删除节点正好在当前选中列表中时,必须从 state 中移除引用,避免 state 持有已删除节点(与 doNotSelect 无关)
|
||||
const selectedNodes = this.get('nodes');
|
||||
const removedSelectedIndex = selectedNodes.findIndex((n: MNode) => `${n.id}` === `${node.id}`);
|
||||
if (removedSelectedIndex !== -1) {
|
||||
const nextSelected = [...selectedNodes];
|
||||
nextSelected.splice(removedSelectedIndex, 1);
|
||||
this.set('nodes', nextSelected);
|
||||
}
|
||||
// 同理,如果被删除的是当前 page,也清空 state.page,避免持有已删除页面
|
||||
if (isPage(node) || isPageFragment(node)) {
|
||||
const currentPage = this.get('page');
|
||||
if (currentPage && `${currentPage.id}` === `${node.id}`) {
|
||||
this.set('page', null);
|
||||
}
|
||||
// 始终清理已删除节点在 state 中的残留引用:
|
||||
// - 即使后续会调用 selectDefault / select(parent) 覆盖,跳过这些调用(doNotSelect / doNotSwitchPage)时也不能让 state 持有已删除节点
|
||||
const selectedNodes = this.get('nodes');
|
||||
const removedSelectedIndex = selectedNodes.findIndex((n: MNode) => `${n.id}` === `${node.id}`);
|
||||
if (removedSelectedIndex !== -1) {
|
||||
const nextSelected = [...selectedNodes];
|
||||
nextSelected.splice(removedSelectedIndex, 1);
|
||||
this.set('nodes', nextSelected);
|
||||
}
|
||||
if (isPage(node) || isPageFragment(node)) {
|
||||
const currentPage = this.get('page');
|
||||
if (currentPage && `${currentPage.id}` === `${node.id}`) {
|
||||
this.set('page', null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,13 +526,14 @@ class Editor extends BaseService {
|
||||
if (isPage(node)) {
|
||||
this.state.pageLength -= 1;
|
||||
|
||||
if (!doNotSelect) {
|
||||
// 删除页面后默认会切到首个剩余页面(selectDefault),doNotSwitchPage 时跳过这次自动切换
|
||||
if (!doNotSelect && !doNotSwitchPage) {
|
||||
await selectDefault(rootItems);
|
||||
}
|
||||
} else if (isPageFragment(node)) {
|
||||
this.state.pageFragmentLength -= 1;
|
||||
|
||||
if (!doNotSelect) {
|
||||
if (!doNotSelect && !doNotSwitchPage) {
|
||||
await selectDefault(rootItems);
|
||||
}
|
||||
} else {
|
||||
@ -534,9 +556,10 @@ class Editor extends BaseService {
|
||||
* @param {Object} node 要删除的节点或节点集合
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面)
|
||||
* @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||
*/
|
||||
public async remove(nodeOrNodeList: MNode | MNode[], options?: { doNotSelect?: boolean }): Promise<void> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
public async remove(nodeOrNodeList: MNode | MNode[], options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
@ -561,7 +584,7 @@ class Editor extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(nodes.map((node) => this.doRemove(node, { doNotSelect })));
|
||||
await Promise.all(nodes.map((node) => this.doRemove(node, { doNotSelect, doNotSwitchPage })));
|
||||
|
||||
if (removedItems.length > 0 && pageForOp) {
|
||||
this.pushOpHistory('remove', { removedItems }, pageForOp);
|
||||
@ -624,8 +647,12 @@ class Editor extends BaseService {
|
||||
this.set('nodes', [...selectedNodes]);
|
||||
}
|
||||
|
||||
// 只有被更新节点正好是当前选中页面时才同步 state.page,避免「更新非当前页」误将编辑器切到该页
|
||||
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
||||
this.set('page', newConfig as MPage | MPageFragment);
|
||||
const currentPage = this.get('page');
|
||||
if (currentPage && `${currentPage.id}` === `${newConfig.id}`) {
|
||||
this.set('page', newConfig as MPage | MPageFragment);
|
||||
}
|
||||
}
|
||||
|
||||
this.addModifiedNodeId(newConfig.id);
|
||||
@ -681,10 +708,11 @@ class Editor extends BaseService {
|
||||
* @param id2 组件ID
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 排序后是否不更新当前选中节点(默认 false)
|
||||
* @param options.doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
* @returns void
|
||||
*/
|
||||
public async sort(id1: Id, id2: Id, options?: { doNotSelect?: boolean }): Promise<void> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
public async sort(id1: Id, id2: Id, options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
@ -750,14 +778,15 @@ class Editor extends BaseService {
|
||||
* @param collectorOptions 可选的依赖收集器配置
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 粘贴后是否不更新当前选中节点(默认 false)
|
||||
* @param options.doNotSwitchPage 粘贴后是否不切换当前页面(默认 false;跨页粘贴时为 true 会跳过页面切换)
|
||||
* @returns 添加后的组件节点配置
|
||||
*/
|
||||
public async paste(
|
||||
position: PastePosition = {},
|
||||
collectorOptions?: TargetOptions,
|
||||
options?: { doNotSelect?: boolean },
|
||||
options?: DslOpOptions,
|
||||
): Promise<MNode | MNode[] | void> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
|
||||
if (!Array.isArray(config)) return;
|
||||
@ -778,7 +807,7 @@ class Editor extends BaseService {
|
||||
propsService.replaceRelateId(config, pasteConfigs, collectorOptions);
|
||||
}
|
||||
|
||||
return this.add(pasteConfigs, parent, { doNotSelect });
|
||||
return this.add(pasteConfigs, parent, { doNotSelect, doNotSwitchPage });
|
||||
}
|
||||
|
||||
public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> {
|
||||
@ -808,10 +837,11 @@ class Editor extends BaseService {
|
||||
* @param config 组件节点配置
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 居中后是否不更新当前选中节点(默认 false)
|
||||
* @param options.doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
* @returns 当前组件节点配置
|
||||
*/
|
||||
public async alignCenter(config: MNode | MNode[], options?: { doNotSelect?: boolean }): Promise<MNode | MNode[]> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
public async alignCenter(config: MNode | MNode[], options?: DslOpOptions): Promise<MNode | MNode[]> {
|
||||
const { doNotSelect = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
const nodes = Array.isArray(config) ? config : [config];
|
||||
const stage = this.get('stage');
|
||||
@ -890,13 +920,10 @@ class Editor extends BaseService {
|
||||
* @param targetId 容器ID
|
||||
* @param options 可选配置
|
||||
* @param options.doNotSelect 移动后是否不更新当前选中节点(默认 false)
|
||||
* @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
|
||||
*/
|
||||
public async moveToContainer(
|
||||
config: MNode,
|
||||
targetId: Id,
|
||||
options?: { doNotSelect?: boolean },
|
||||
): Promise<MNode | undefined> {
|
||||
const { doNotSelect = false } = safeOptions<{ doNotSelect?: boolean }>(options);
|
||||
public async moveToContainer(config: MNode, targetId: Id, options?: DslOpOptions): Promise<MNode | undefined> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
@ -925,7 +952,11 @@ class Editor extends BaseService {
|
||||
|
||||
target.items.push(newConfig);
|
||||
|
||||
if (!doNotSelect) {
|
||||
// 目标容器是否在非当前页面:选中目标会触发当前页面切换
|
||||
const targetWouldSwitchPage = this.isOnDifferentPage(target);
|
||||
const skipSelect = doNotSelect || (doNotSwitchPage && targetWouldSwitchPage);
|
||||
|
||||
if (!skipSelect) {
|
||||
await stage.select(targetId);
|
||||
}
|
||||
|
||||
@ -936,7 +967,7 @@ class Editor extends BaseService {
|
||||
root: cloneDeep(root),
|
||||
});
|
||||
|
||||
if (!doNotSelect) {
|
||||
if (!skipSelect) {
|
||||
await this.select(newConfig);
|
||||
stage.select(newConfig.id);
|
||||
}
|
||||
|
||||
@ -873,3 +873,13 @@ export const canUsePluginMethods = {
|
||||
};
|
||||
|
||||
export type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
|
||||
|
||||
/**
|
||||
* DSL 修改类操作的通用配置
|
||||
* - doNotSelect: 操作后是否不要自动触发选中(不调用 this.select / this.multiSelect / stage.select / stage.multiSelect)
|
||||
* - doNotSwitchPage: 操作若会引发当前页面切换(如新增 / 删除 / 跨页移动),是否跳过这次切换
|
||||
*/
|
||||
export type DslOpOptions = {
|
||||
doNotSelect?: boolean;
|
||||
doNotSwitchPage?: boolean;
|
||||
};
|
||||
|
||||
@ -190,6 +190,43 @@ describe('getParentById', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOnDifferentPage', () => {
|
||||
test('当前未选中任何页面时返回 false', () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
editorService.resetState();
|
||||
const pageNode = editorService.getNodeById(NodeId.PAGE_ID)!;
|
||||
expect(editorService.isOnDifferentPage(pageNode)).toBe(false);
|
||||
});
|
||||
|
||||
test('节点在当前页面内时返回 false', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const innerNode = editorService.getNodeById(NodeId.NODE_ID)!;
|
||||
expect(editorService.isOnDifferentPage(innerNode)).toBe(false);
|
||||
});
|
||||
|
||||
test('页面节点本身就是当前页面时返回 false', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const pageNode = editorService.getNodeById(NodeId.PAGE_ID)!;
|
||||
expect(editorService.isOnDifferentPage(pageNode)).toBe(false);
|
||||
});
|
||||
|
||||
test('节点位于非当前页面时返回 true', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
// 加一个新页面
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode, { doNotSwitchPage: true });
|
||||
const newPageId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
|
||||
// 当前还在第一个页面
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
const newPageNode = editorService.getNodeById(newPageId)!;
|
||||
expect(editorService.isOnDifferentPage(newPageNode)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('select', () => {
|
||||
beforeAll(() => editorService.set('root', cloneDeep(root)));
|
||||
|
||||
@ -303,6 +340,24 @@ describe('add', () => {
|
||||
// 但当前选中节点保持原状(未自动选中新增节点)
|
||||
expect(editorService.get('node')?.id).toBe(beforeNodeId);
|
||||
});
|
||||
|
||||
test('doNotSwitchPage: true 新增页面时保持当前页面不切换', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const beforePageId = editorService.get('page')?.id;
|
||||
expect(beforePageId).toBe(NodeId.PAGE_ID);
|
||||
|
||||
const rootNode = editorService.get('root');
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode, { doNotSwitchPage: true });
|
||||
|
||||
// 新页面已加入 dsl
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
expect(editorService.getNodeById(addedId)).toBeTruthy();
|
||||
expect(rootNode?.items.length).toBe(2);
|
||||
|
||||
// 当前 page 保持不变(没有自动切到新加的页面)
|
||||
expect(editorService.get('page')?.id).toBe(beforePageId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
@ -363,6 +418,51 @@ describe('remove', () => {
|
||||
// state.nodes 中不再包含被删除的节点
|
||||
expect(editorService.get('nodes').some((n) => n.id === NodeId.NODE_ID)).toBe(false);
|
||||
});
|
||||
|
||||
test('doNotSwitchPage: true 删除当前页面后不自动切到其它页面', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
// 先加一个页面,确保 root 下有 2 个页面
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
expect(rootNode?.items.length).toBe(2);
|
||||
|
||||
// 选中第一个页面,作为当前页面
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
|
||||
// 删除当前页面,并要求不切换页面
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE }, { doNotSwitchPage: true });
|
||||
|
||||
// 被删除页面在 dsl 中确实已不存在
|
||||
expect(editorService.getNodeById(NodeId.PAGE_ID)).toBeNull();
|
||||
// 当前 page 引用被清空,不会被自动切到剩余页面
|
||||
expect(editorService.get('page')).toBeNull();
|
||||
// 仍保留 newPage 在 dsl 中
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
expect(editorService.getNodeById(addedId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('默认删除当前页面后会自动切到剩余首个页面', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
// 先加一个页面
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
expect(rootNode?.items.length).toBe(2);
|
||||
|
||||
// 选中第一个页面作为当前页面
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
|
||||
// 删除当前页面,使用默认行为
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE });
|
||||
|
||||
// 被删除页面在 dsl 中已不存在
|
||||
expect(editorService.getNodeById(NodeId.PAGE_ID)).toBeNull();
|
||||
// 自动切到剩余首个页面
|
||||
expect(editorService.get('page')?.id).toBe(addedId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
@ -439,6 +539,27 @@ describe('update', () => {
|
||||
expect(editorService.get('node')?.id).toBe(NodeId.NODE_ID);
|
||||
expect(editorService.get('node')).toBe(beforeSelected);
|
||||
});
|
||||
|
||||
test('更新非当前页面时,不会把编辑器切到该页', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
// 先加一个新页面
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
const newPageId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
|
||||
// 选中第一个页面作为当前页
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
|
||||
// 更新非当前页面(newPage)的配置
|
||||
await editorService.update({ id: newPageId, type: NodeType.PAGE, name: 'page-renamed' });
|
||||
|
||||
// dsl 中该页已更新
|
||||
expect(editorService.getNodeById(newPageId)?.name).toBe('page-renamed');
|
||||
// 当前 page 没有被切走
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sort', () => {
|
||||
@ -495,6 +616,24 @@ describe('paste', () => {
|
||||
// 当前选中节点保持原状
|
||||
expect(editorService.get('node')?.id).toBe(beforeNodeId);
|
||||
});
|
||||
|
||||
test('doNotSwitchPage: true 粘贴页面时不切换当前页面', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
|
||||
// 复制当前页面
|
||||
const pageNode = editorService.getNodeById(NodeId.PAGE_ID);
|
||||
await editorService.copy(pageNode!);
|
||||
const beforePageId = editorService.get('page')?.id;
|
||||
expect(beforePageId).toBe(NodeId.PAGE_ID);
|
||||
|
||||
const pasted = await editorService.paste({}, undefined, { doNotSwitchPage: true });
|
||||
|
||||
// 粘贴成功
|
||||
expect(pasted).toBeTruthy();
|
||||
// 当前 page 保持不变
|
||||
expect(editorService.get('page')?.id).toBe(beforePageId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveLayer', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user