mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-30 04:08:04 +00:00
refactor(editor): 移除 BaseService 废弃的 use/middleware 机制
- 删除已 @deprecated 的 BaseService.use 方法及其 middleware 通道 - 删除 utils/compose.ts 及对应测试(仅服务于 middleware,无其他引用) - editor.ts 移除 safeOptions/safeParent 兜底,相关方法 options 改用形参默认值 - props.ts fillConfig 的 labelWidth 改为形参默认值,移除 typeof function 兜底 - 同步更新 5 份 service 方法文档,删除 ## use 章节
This commit is contained in:
parent
d01a28ce76
commit
de94a75803
@ -288,15 +288,11 @@
|
||||
|
||||
销毁 codeBlockService,重置状态并移除所有事件监听和插件
|
||||
|
||||
## use
|
||||
|
||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
||||
|
||||
## usePlugin
|
||||
|
||||
- **详情:**
|
||||
|
||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
|
||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||
|
||||
|
||||
@ -712,45 +712,11 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
||||
|
||||
移除所有事件监听,清空state,移除所有插件
|
||||
|
||||
## use
|
||||
|
||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
||||
|
||||
- **示例:**
|
||||
|
||||
```js
|
||||
import { editorService, getAddParent } from "@tmagic/editor";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
editorService.use({
|
||||
// 添加是否删除节点确认提示
|
||||
async remove(node, next) {
|
||||
await ElMessageBox.confirm("是否删除", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
add(node, next) {
|
||||
// text组件只能添加到container中
|
||||
const parentNode = getAddParent(node);
|
||||
if (node.type === "text" && parentNode?.type !== "container") {
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## usePlugin
|
||||
|
||||
- **详情:**
|
||||
|
||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
|
||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||
|
||||
|
||||
@ -254,15 +254,11 @@
|
||||
|
||||
销毁propsService
|
||||
|
||||
## use
|
||||
|
||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
||||
|
||||
## usePlugin
|
||||
|
||||
- **详情:**
|
||||
|
||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
|
||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||
|
||||
|
||||
@ -231,28 +231,11 @@ import { storageService } from '@tmagic/editor';
|
||||
storageService.destroy();
|
||||
```
|
||||
|
||||
## use
|
||||
|
||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
||||
|
||||
- **示例:**
|
||||
|
||||
```js
|
||||
import { storageService } from '@tmagic/editor';
|
||||
|
||||
storageService.use({
|
||||
getItem(key, options, next) {
|
||||
console.log('获取存储项:', key);
|
||||
return next();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## usePlugin
|
||||
|
||||
- **详情:**
|
||||
|
||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
|
||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||
|
||||
|
||||
@ -179,29 +179,11 @@ import { uiService } from '@tmagic/editor';
|
||||
uiService.destroy();
|
||||
```
|
||||
|
||||
## use
|
||||
|
||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
||||
|
||||
- **示例:**
|
||||
|
||||
```js
|
||||
import { uiService } from '@tmagic/editor';
|
||||
|
||||
uiService.use({
|
||||
async zoom(value, next) {
|
||||
console.log('缩放前:', uiService.get('zoom'));
|
||||
await next();
|
||||
console.log('缩放后:', uiService.get('zoom'));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## usePlugin
|
||||
|
||||
- **详情:**
|
||||
|
||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||
|
||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
@ -19,45 +18,32 @@
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { compose } from '@editor/utils/compose';
|
||||
|
||||
const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpperCase()}${name.substring(1)}`;
|
||||
|
||||
const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
|
||||
|
||||
const doAction = (
|
||||
args: any[],
|
||||
scope: any,
|
||||
sourceMethod: any,
|
||||
beforeMethodName: string,
|
||||
afterMethodName: string,
|
||||
fn: (args: any[], next?: Function | undefined) => void,
|
||||
) => {
|
||||
try {
|
||||
let beforeArgs = args;
|
||||
const doAction = (args: any[], scope: any, sourceMethod: any, beforeMethodName: string, afterMethodName: string) => {
|
||||
let beforeArgs = args;
|
||||
|
||||
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
||||
beforeArgs = beforeMethod(...beforeArgs) || [];
|
||||
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
||||
beforeArgs = beforeMethod(...beforeArgs) || [];
|
||||
|
||||
if (isError(beforeArgs)) throw beforeArgs;
|
||||
if (isError(beforeArgs)) throw beforeArgs;
|
||||
|
||||
if (!Array.isArray(beforeArgs)) {
|
||||
beforeArgs = [beforeArgs];
|
||||
}
|
||||
if (!Array.isArray(beforeArgs)) {
|
||||
beforeArgs = [beforeArgs];
|
||||
}
|
||||
|
||||
let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope));
|
||||
|
||||
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
||||
returnValue = afterMethod(returnValue, ...beforeArgs);
|
||||
|
||||
if (isError(returnValue)) throw returnValue;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let returnValue: any = sourceMethod.apply(scope, beforeArgs);
|
||||
|
||||
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
||||
returnValue = afterMethod(returnValue, ...beforeArgs);
|
||||
|
||||
if (isError(returnValue)) throw returnValue;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
const doAsyncAction = async (
|
||||
@ -66,40 +52,34 @@ const doAsyncAction = async (
|
||||
sourceMethod: any,
|
||||
beforeMethodName: string,
|
||||
afterMethodName: string,
|
||||
fn: (args: any[], next?: Function | undefined) => Promise<void> | void,
|
||||
) => {
|
||||
try {
|
||||
let beforeArgs = args;
|
||||
let beforeArgs = args;
|
||||
|
||||
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
||||
beforeArgs = (await beforeMethod(...beforeArgs)) || [];
|
||||
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
||||
beforeArgs = (await beforeMethod(...beforeArgs)) || [];
|
||||
|
||||
if (isError(beforeArgs)) throw beforeArgs;
|
||||
if (isError(beforeArgs)) throw beforeArgs;
|
||||
|
||||
if (!Array.isArray(beforeArgs)) {
|
||||
beforeArgs = [beforeArgs];
|
||||
}
|
||||
if (!Array.isArray(beforeArgs)) {
|
||||
beforeArgs = [beforeArgs];
|
||||
}
|
||||
|
||||
let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope));
|
||||
|
||||
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
||||
returnValue = await afterMethod(returnValue, ...beforeArgs);
|
||||
|
||||
if (isError(returnValue)) throw returnValue;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let returnValue: any = await sourceMethod.apply(scope, beforeArgs);
|
||||
|
||||
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
||||
returnValue = await afterMethod(returnValue, ...beforeArgs);
|
||||
|
||||
if (isError(returnValue)) throw returnValue;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* 提供两种方式对Class进行扩展
|
||||
* 方法1:
|
||||
* 对Class进行扩展
|
||||
* 给Class中的每个方法都添加before after两个钩子
|
||||
* 给Class添加一个usePlugin方法,use方法可以传入一个包含before或者after方法的对象
|
||||
* 给Class添加一个usePlugin方法,usePlugin方法可以传入一个包含before或者after方法的对象
|
||||
*
|
||||
* 例如:
|
||||
* Class EditorService extends BaseService {
|
||||
@ -124,27 +104,9 @@ const doAsyncAction = async (
|
||||
*
|
||||
* 调用时的参数会透传到before方法的参数中, 然后before的return 会作为原方法的参数和after的参数,after第一个参数则是原方法的return值;
|
||||
* 如需终止后续方法调用可以return new Error();
|
||||
*
|
||||
* 方法2:
|
||||
* 给Class中的每个方法都添加中间件
|
||||
* 给Class添加一个use方法,use方法可以传入一个包含源对象方法名作为key值的对象
|
||||
*
|
||||
* 例如:
|
||||
* Class EditorService extends BaseService {
|
||||
* constructor() {
|
||||
* super([ { name: 'add', isAsync: true },]);
|
||||
* }
|
||||
* add(value) { return result; }
|
||||
* };
|
||||
*
|
||||
* const editorService = new EditorService();
|
||||
* editorService.use({
|
||||
* add(value, next) { console.log(value); next() },
|
||||
* });
|
||||
*/
|
||||
export default class extends EventEmitter {
|
||||
class BaseService extends EventEmitter {
|
||||
private pluginOptionsList: Record<string, Function[]> = {};
|
||||
private middleware: Record<string, Function[]> = {};
|
||||
private taskList: (() => Promise<void>)[] = [];
|
||||
private doingTask = false;
|
||||
|
||||
@ -161,14 +123,12 @@ export default class extends EventEmitter {
|
||||
|
||||
this.pluginOptionsList[beforeMethodName] = [];
|
||||
this.pluginOptionsList[afterMethodName] = [];
|
||||
this.middleware[propertyName] = [];
|
||||
|
||||
const fn = compose(this.middleware[propertyName], isAsync);
|
||||
Object.defineProperty(scope, propertyName, {
|
||||
value: isAsync
|
||||
? async (...args: any[]) => {
|
||||
if (!serialMethods.includes(propertyName)) {
|
||||
return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName);
|
||||
}
|
||||
|
||||
// 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
|
||||
@ -176,7 +136,7 @@ export default class extends EventEmitter {
|
||||
const promise = new Promise<any>((resolve, reject) => {
|
||||
this.taskList.push(async () => {
|
||||
try {
|
||||
const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName);
|
||||
resolve(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@ -190,20 +150,11 @@ export default class extends EventEmitter {
|
||||
|
||||
return promise;
|
||||
}
|
||||
: (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn),
|
||||
: (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 请使用usePlugin代替
|
||||
*/
|
||||
public use(options: Record<string, Function>) {
|
||||
for (const [methodName, method] of Object.entries(options)) {
|
||||
if (typeof method === 'function') this.middleware[methodName].push(method);
|
||||
}
|
||||
}
|
||||
|
||||
public usePlugin(options: Record<string, Function>) {
|
||||
for (const [methodName, method] of Object.entries(options)) {
|
||||
if (typeof method === 'function' && !this.pluginOptionsList[methodName].includes(method)) {
|
||||
@ -224,10 +175,6 @@ export default class extends EventEmitter {
|
||||
for (const key of Object.keys(this.pluginOptionsList)) {
|
||||
this.pluginOptionsList[key] = [];
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.middleware)) {
|
||||
this.middleware[key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async doTask() {
|
||||
@ -240,3 +187,5 @@ export default class extends EventEmitter {
|
||||
this.doingTask = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseService;
|
||||
|
||||
@ -66,25 +66,6 @@ import type { HistoryOpContext } from '@editor/utils/editor-history';
|
||||
import { applyHistoryAddOp, applyHistoryRemoveOp, applyHistoryUpdateOp } from '@editor/utils/editor-history';
|
||||
import { beforePaste, getAddParent } from '@editor/utils/operator';
|
||||
|
||||
/**
|
||||
* 经过 BaseService 的插件 / 中间件包装后,源方法的最后一个形参可能被注入为 dispatch 函数
|
||||
* 当 options 形参位置被注入为函数(或为 null)时,将其归一为空对象,避免后续逻辑误读
|
||||
*/
|
||||
const safeOptions = <T extends object>(options: unknown): T => {
|
||||
const empty = {};
|
||||
if (!options || typeof options === 'function') return empty as T;
|
||||
return options as T;
|
||||
};
|
||||
|
||||
/**
|
||||
* 经过 BaseService 的插件 / 中间件包装后,源方法的形参可能被注入为 dispatch 函数
|
||||
* 当 parent 形参位置被注入为函数(或为空值)时,归一为 null,由调用方继续走默认 parent 逻辑
|
||||
*/
|
||||
const safeParent = (parent: unknown): MContainer | null => {
|
||||
if (!parent || typeof parent === 'function') return null;
|
||||
return parent as MContainer;
|
||||
};
|
||||
|
||||
class Editor extends BaseService {
|
||||
public state: StoreState = reactive({
|
||||
root: null,
|
||||
@ -212,7 +193,7 @@ class Editor extends BaseService {
|
||||
* 只有容器拥有布局
|
||||
*/
|
||||
public async getLayout(parent: MNode, node?: MNode | null): Promise<Layout> {
|
||||
if (node && typeof node !== 'function' && isFixed(node.style || {})) return Layout.FIXED;
|
||||
if (node && isFixed(node.style || {})) return Layout.FIXED;
|
||||
|
||||
if (parent.layout) {
|
||||
return parent.layout;
|
||||
@ -393,11 +374,8 @@ class Editor extends BaseService {
|
||||
public async add(
|
||||
addNode: AddMNode | MNode[],
|
||||
parent?: MContainer | null,
|
||||
options?: DslOpOptions,
|
||||
{ doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {},
|
||||
): Promise<MNode | MNode[]> {
|
||||
const safeParentNode = safeParent(parent);
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
const stage = this.get('stage');
|
||||
@ -420,7 +398,7 @@ class Editor extends BaseService {
|
||||
if ((isPage(node) || isPageFragment(node)) && root) {
|
||||
return this.doAdd(node, root);
|
||||
}
|
||||
const parentNode = safeParentNode ?? getAddParent(node);
|
||||
const parentNode = parent ?? getAddParent(node);
|
||||
if (!parentNode) throw new Error('未找到父元素');
|
||||
return this.doAdd(node, parentNode);
|
||||
}),
|
||||
@ -476,9 +454,10 @@ class Editor extends BaseService {
|
||||
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
||||
}
|
||||
|
||||
public async doRemove(node: MNode, options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
public async doRemove(
|
||||
node: MNode,
|
||||
{ doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {},
|
||||
): Promise<void> {
|
||||
const root = this.get('root');
|
||||
if (!root) throw new Error('root不能为空');
|
||||
|
||||
@ -558,9 +537,10 @@ class Editor extends BaseService {
|
||||
* @param options.doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面)
|
||||
* @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||
*/
|
||||
public async remove(nodeOrNodeList: MNode | MNode[], options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
public async remove(
|
||||
nodeOrNodeList: MNode | MNode[],
|
||||
{ doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {},
|
||||
): Promise<void> {
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
||||
@ -711,9 +691,7 @@ class Editor extends BaseService {
|
||||
* @param options.doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
* @returns void
|
||||
*/
|
||||
public async sort(id1: Id, id2: Id, options?: DslOpOptions): Promise<void> {
|
||||
const { doNotSelect = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
public async sort(id1: Id, id2: Id, { doNotSelect = false }: DslOpOptions = {}): Promise<void> {
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
const root = this.get('root');
|
||||
@ -784,10 +762,8 @@ class Editor extends BaseService {
|
||||
public async paste(
|
||||
position: PastePosition = {},
|
||||
collectorOptions?: TargetOptions,
|
||||
options?: DslOpOptions,
|
||||
{ doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {},
|
||||
): Promise<MNode | MNode[] | void> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
|
||||
if (!Array.isArray(config)) return;
|
||||
|
||||
@ -840,9 +816,10 @@ class Editor extends BaseService {
|
||||
* @param options.doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||
* @returns 当前组件节点配置
|
||||
*/
|
||||
public async alignCenter(config: MNode | MNode[], options?: DslOpOptions): Promise<MNode | MNode[]> {
|
||||
const { doNotSelect = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
public async alignCenter(
|
||||
config: MNode | MNode[],
|
||||
{ doNotSelect = false }: DslOpOptions = {},
|
||||
): Promise<MNode | MNode[]> {
|
||||
const nodes = Array.isArray(config) ? config : [config];
|
||||
const stage = this.get('stage');
|
||||
|
||||
@ -922,9 +899,11 @@ class Editor extends BaseService {
|
||||
* @param options.doNotSelect 移动后是否不更新当前选中节点(默认 false)
|
||||
* @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
|
||||
*/
|
||||
public async moveToContainer(config: MNode, targetId: Id, options?: DslOpOptions): Promise<MNode | undefined> {
|
||||
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions<DslOpOptions>(options);
|
||||
|
||||
public async moveToContainer(
|
||||
config: MNode,
|
||||
targetId: Id,
|
||||
{ doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {},
|
||||
): Promise<MNode | undefined> {
|
||||
this.captureSelectionBeforeOp();
|
||||
|
||||
const root = this.get('root');
|
||||
|
||||
@ -97,9 +97,9 @@ class Props extends BaseService {
|
||||
return this.state.propsConfigMap;
|
||||
}
|
||||
|
||||
public async fillConfig(config: FormConfig, labelWidth?: string) {
|
||||
public async fillConfig(config: FormConfig, labelWidth = '80px') {
|
||||
return fillConfig(config, {
|
||||
labelWidth: typeof labelWidth !== 'function' ? labelWidth : '80px',
|
||||
labelWidth,
|
||||
disabledDataSource: this.getDisabledDataSource(),
|
||||
disabledCodeBlock: this.getDisabledCodeBlock(),
|
||||
});
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @param {Array} middleware
|
||||
* @return {Function}
|
||||
*/
|
||||
export const compose = (middleware: Function[], isAsync: boolean) => {
|
||||
if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!');
|
||||
for (const fn of middleware) {
|
||||
if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
*/
|
||||
return (args: any[], next?: Function) => {
|
||||
// last called middleware #
|
||||
let index = -1;
|
||||
return dispatch(0);
|
||||
function dispatch(i: number): Promise<void> | void {
|
||||
if (i <= index) {
|
||||
const error = new Error('next() 被多次调用');
|
||||
if (isAsync) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
index = i;
|
||||
let fn = middleware[i];
|
||||
if (i === middleware.length && next) fn = next;
|
||||
if (!fn) {
|
||||
if (isAsync) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
try {
|
||||
return Promise.resolve(fn(...args, dispatch.bind(null, i + 1)));
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return fn(...args, dispatch.bind(null, i + 1));
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -43,20 +43,6 @@ describe('BaseService 同步方法 + plugin', () => {
|
||||
expect(result).toBe(30);
|
||||
});
|
||||
|
||||
test('use 添加同步 middleware', () => {
|
||||
const svc = new SyncService();
|
||||
const order: string[] = [];
|
||||
svc.use({
|
||||
add(_value: number, next: Function) {
|
||||
order.push('mw-before');
|
||||
next();
|
||||
order.push('mw-after');
|
||||
},
|
||||
});
|
||||
svc.add(1);
|
||||
expect(order).toEqual(['mw-before', 'mw-after']);
|
||||
});
|
||||
|
||||
test('removePlugin 解除指定钩子', () => {
|
||||
const svc = new SyncService();
|
||||
const before = vi.fn((v: number) => [v]);
|
||||
@ -66,7 +52,7 @@ describe('BaseService 同步方法 + plugin', () => {
|
||||
expect(before).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('removeAllPlugins 清空所有 plugin/middleware', () => {
|
||||
test('removeAllPlugins 清空所有 plugin', () => {
|
||||
const svc = new SyncService();
|
||||
const before = vi.fn((v: number) => [v]);
|
||||
svc.usePlugin({ beforeAdd: before });
|
||||
|
||||
@ -648,35 +648,6 @@ describe('moveLayer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('插件参数兜底', () => {
|
||||
test('add 的 parent 形参传入函数时不抛错,仍走默认父节点逻辑', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.NODE_ID);
|
||||
|
||||
// 模拟 BaseService 中间件机制在 parent 位置注入 dispatch 函数
|
||||
const dispatchFn = () => {};
|
||||
const newNode = await editorService.add({ type: 'text' }, dispatchFn as any);
|
||||
|
||||
// 默认行为:被加到了当前选中节点的父节点 (PAGE)
|
||||
const addedId = Array.isArray(newNode) ? newNode[0].id : newNode.id;
|
||||
const parentInfo = editorService.getParentById(addedId);
|
||||
expect(parentInfo?.id).toBe(NodeId.PAGE_ID);
|
||||
});
|
||||
|
||||
test('add 的 options 形参传入函数时不抛错,doNotSelect 回落为默认值', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.NODE_ID);
|
||||
|
||||
// 模拟 BaseService 中间件机制在 options 位置注入 dispatch 函数
|
||||
const dispatchFn = () => {};
|
||||
const newNode = await editorService.add({ type: 'text' }, null, dispatchFn as any);
|
||||
|
||||
// 默认行为:当前选中节点变成了新增节点
|
||||
const addedId = Array.isArray(newNode) ? newNode[0].id : newNode.id;
|
||||
expect(editorService.get('node')?.id).toBe(addedId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('undo redo', () => {
|
||||
beforeAll(() => editorService.set('root', cloneDeep(root)));
|
||||
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
* Copyright (C) 2025 Tencent.
|
||||
*/
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { compose } from '@editor/utils/compose';
|
||||
|
||||
describe('compose 同步', () => {
|
||||
test('依次调用 middleware 并通过 next 串行', () => {
|
||||
const order: number[] = [];
|
||||
const m1 = (next: Function) => {
|
||||
order.push(1);
|
||||
next();
|
||||
order.push(4);
|
||||
};
|
||||
const m2 = (next: Function) => {
|
||||
order.push(2);
|
||||
next();
|
||||
order.push(3);
|
||||
};
|
||||
compose([m1, m2], false)([]);
|
||||
expect(order).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('next() 多次调用抛错', () => {
|
||||
const m = (next: Function) => {
|
||||
next();
|
||||
next();
|
||||
};
|
||||
expect(() => compose([m], false)([])).toThrow(/next\(\) 被多次调用/);
|
||||
});
|
||||
|
||||
test('参数 fn 不是函数抛错', () => {
|
||||
expect(() => compose([null as any], false)([])).toThrow(/Middleware 必须由函数组成/);
|
||||
});
|
||||
|
||||
test('参数不是数组抛错', () => {
|
||||
expect(() => compose('x' as any, false)([])).toThrow(/Middleware 必须是一个数组/);
|
||||
});
|
||||
|
||||
test('支持 next 参数透传', () => {
|
||||
const order: number[] = [];
|
||||
const tail = () => order.push(99);
|
||||
compose(
|
||||
[
|
||||
(next: Function) => {
|
||||
order.push(1);
|
||||
next();
|
||||
},
|
||||
],
|
||||
false,
|
||||
)([], tail);
|
||||
expect(order).toEqual([1, 99]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compose 异步', () => {
|
||||
test('返回 Promise,按 next 顺序执行', async () => {
|
||||
const order: number[] = [];
|
||||
const m1 = async (next: Function) => {
|
||||
order.push(1);
|
||||
await next();
|
||||
order.push(4);
|
||||
};
|
||||
const m2 = async (next: Function) => {
|
||||
order.push(2);
|
||||
await next();
|
||||
order.push(3);
|
||||
};
|
||||
await compose([m1, m2], true)([]);
|
||||
expect(order).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('next 多次调用 reject', async () => {
|
||||
const m = async (next: Function) => {
|
||||
await next();
|
||||
await next();
|
||||
};
|
||||
await expect(compose([m], true)([])).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
test('middleware 抛同步错时 reject', async () => {
|
||||
const m = () => {
|
||||
throw new Error('boom');
|
||||
};
|
||||
await expect(compose([m as any], true)([])).rejects.toThrow('boom');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user