refactor(editor): remove dependency of command global types (#9903)

Closes: [BS-2216](https://linear.app/affine-design/issue/BS-2216/remove-global-types-in-command)
This commit is contained in:
Saul-Mirone
2025-01-27 12:28:46 +00:00
parent 4b549e0484
commit 17bf75e843
170 changed files with 1461 additions and 2124 deletions
@@ -1,16 +1,6 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { LifeCycleWatcher } from '../extension/index.js';
import { CommandIdentifier } from '../identifier.js';
import { cmdSymbol } from './consts.js';
import type {
Chain,
Command,
ExecCommandResult,
IfAllKeysOptional,
InDataOfCommand,
InitCommandCtx,
} from './types.js';
import type { Chain, Command, InitCommandCtx } from './types.js';
/**
* Command manager to manage all commands
@@ -128,12 +118,7 @@ import type {
export class CommandManager extends LifeCycleWatcher {
static override readonly key = 'commandManager';
private readonly _commands = new Map<string, Command>();
private readonly _createChain = (
methods: Record<BlockSuite.CommandName, unknown>,
_cmds: Command[]
): Chain => {
private readonly _createChain = (_cmds: Command[]): Chain => {
const getCommandCtx = this._getCommandCtx;
const createChain = this._createChain;
const chain = this.chain;
@@ -145,7 +130,7 @@ export class CommandManager extends LifeCycleWatcher {
let success = false;
try {
const cmds = this[cmdSymbol];
ctx = runCmds(ctx as BlockSuite.CommandContext, [
ctx = runCmds(ctx, [
...cmds,
(_, next) => {
success = true;
@@ -160,38 +145,35 @@ export class CommandManager extends LifeCycleWatcher {
},
with: function (this: Chain, value) {
const cmds = this[cmdSymbol];
return createChain(methods, [
...cmds,
(_, next) => next(value),
]) as never;
return createChain([...cmds, (_, next) => next(value)]) as never;
},
inline: function (this: Chain, command) {
pipe: function (this: Chain, command: Command, input?: object) {
const cmds = this[cmdSymbol];
return createChain(methods, [...cmds, command]) as never;
return createChain([
...cmds,
(ctx, next) => command({ ...ctx, ...input }, next),
]);
},
try: function (this: Chain, fn) {
const cmds = this[cmdSymbol];
return createChain(methods, [
return createChain([
...cmds,
(beforeCtx, next) => {
let ctx = beforeCtx;
const chains = fn(chain());
chains.some(chain => {
// inject ctx in the beginning
chain[cmdSymbol] = [
const commands = fn(chain());
commands.some(innerChain => {
innerChain[cmdSymbol] = [
(_, next) => {
next(ctx);
},
...chain[cmdSymbol],
...innerChain[cmdSymbol],
];
const [success] = chain
.inline((branchCtx, next) => {
ctx = { ...ctx, ...branchCtx };
next();
})
.run();
const [success, branchCtx] = innerChain.run();
ctx = { ...ctx, ...branchCtx };
if (success) {
next(ctx);
return true;
@@ -203,40 +185,38 @@ export class CommandManager extends LifeCycleWatcher {
},
tryAll: function (this: Chain, fn) {
const cmds = this[cmdSymbol];
return createChain(methods, [
return createChain([
...cmds,
(beforeCtx, next) => {
let ctx = beforeCtx;
const chains = fn(chain());
let allFail = true;
chains.forEach(chain => {
// inject ctx in the beginning
chain[cmdSymbol] = [
const commands = fn(chain());
commands.forEach(innerChain => {
innerChain[cmdSymbol] = [
(_, next) => {
next(ctx);
},
...chain[cmdSymbol],
...innerChain[cmdSymbol],
];
const [success] = chain
.inline((branchCtx, next) => {
ctx = { ...ctx, ...branchCtx };
next();
})
.run();
const [success, branchCtx] = innerChain.run();
ctx = { ...ctx, ...branchCtx };
if (success) {
allFail = false;
}
});
if (!allFail) {
next(ctx);
}
},
]) as never;
},
...methods,
} as Chain;
};
};
private readonly _getCommandCtx = (): InitCommandCtx => {
@@ -258,120 +238,18 @@ export class CommandManager extends LifeCycleWatcher {
* data is the final context after running the chain
*/
chain = (): Chain<InitCommandCtx> => {
const methods = {} as Record<
string,
(data: Record<string, unknown>) => Chain
>;
const createChain = this._createChain;
for (const [name, command] of this._commands.entries()) {
methods[name] = function (
this: { [cmdSymbol]: Command[] },
data: Record<string, unknown>
) {
const cmds = this[cmdSymbol];
return createChain(methods, [
...cmds,
(ctx, next) => command({ ...ctx, ...data }, next),
]);
};
}
return createChain(methods, []) as never;
return this._createChain([]);
};
/**
* Register a command to the command manager
* @param name
* @param command
* Make sure to also add the command to the global interface `BlockSuite.Commands`
* ```ts
* const myCommand: Command = (ctx, next) => {
* // do something
* }
*
* declare global {
* namespace BlockSuite {
* interface Commands {
* 'myCommand': typeof myCommand
* }
* }
* }
* ```
*/
add<N extends BlockSuite.CommandName>(
name: N,
command: BlockSuite.Commands[N]
): CommandManager;
add(name: string, command: Command) {
this._commands.set(name, command);
return this;
}
override created() {
const add = this.add.bind(this);
this.std.provider.getAll(CommandIdentifier).forEach((command, key) => {
add(key as keyof BlockSuite.Commands, command);
});
}
/**
* Execute a registered command by name
* @param command
* @param payloads
* ```ts
* const { success, ...data } = commandManager.exec('myCommand', { data: 'data' });
* ```
* @returns { success, ...data } - success is a boolean to indicate if the command is successful,
* data is the final context after running the command
*/
exec<K extends keyof BlockSuite.Commands>(
command: K,
...payloads: IfAllKeysOptional<
Omit<InDataOfCommand<BlockSuite.Commands[K]>, keyof InitCommandCtx>,
[
inData: void | Omit<
InDataOfCommand<BlockSuite.Commands[K]>,
keyof InitCommandCtx
>,
],
[
inData: Omit<
InDataOfCommand<BlockSuite.Commands[K]>,
keyof InitCommandCtx
>,
]
>
): ExecCommandResult<K> & { success: boolean } {
const cmdFunc = this._commands.get(command);
if (!cmdFunc) {
throw new BlockSuiteError(
ErrorCode.CommandError,
`The command "${command}" not found`
);
}
const inData = payloads[0];
const ctx = {
...this._getCommandCtx(),
...inData,
};
let execResult = {
success: false,
} as ExecCommandResult<K> & { success: boolean };
cmdFunc(ctx, result => {
// @ts-expect-error FIXME: ts error
execResult = { ...result, success: true };
});
return execResult;
}
exec = <Output extends object, Input extends object>(
command: Command<Input, Output>,
input?: Input
) => {
return this.chain().pipe(command, input).run();
};
}
function runCmds(ctx: BlockSuite.CommandContext, [cmd, ...rest]: Command[]) {
function runCmds(ctx: InitCommandCtx, [cmd, ...rest]: Command[]) {
let _ctx = ctx;
if (cmd) {
cmd(ctx, data => {