mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
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:
@@ -4,33 +4,15 @@ import type { Command } from '../command/index.js';
|
||||
import { CommandManager } from '../command/index.js';
|
||||
|
||||
type Command1 = Command<
|
||||
never,
|
||||
'commandData1',
|
||||
{
|
||||
command1Option?: string;
|
||||
},
|
||||
{
|
||||
commandData1: string;
|
||||
}
|
||||
>;
|
||||
|
||||
type Command2 = Command<'commandData1', 'commandData2'>;
|
||||
|
||||
type Command3 = Command<'commandData1' | 'commandData2', 'commandData3'>;
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface CommandContext {
|
||||
commandData1?: string;
|
||||
commandData2?: string;
|
||||
commandData3?: string;
|
||||
}
|
||||
|
||||
interface Commands {
|
||||
command1: Command1;
|
||||
command2: Command2;
|
||||
command3: Command3;
|
||||
command4: Command;
|
||||
}
|
||||
}
|
||||
}
|
||||
type Command2 = Command<{ commandData1: string }, { commandData2: string }>;
|
||||
|
||||
describe('CommandManager', () => {
|
||||
let std: BlockSuite.Std;
|
||||
@@ -43,13 +25,15 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('can add and execute a command', () => {
|
||||
const command1: Command = vi.fn((_ctx, next) => next());
|
||||
const command2: Command = vi.fn((_ctx, _next) => {});
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
const command1: Command1 = vi.fn((_ctx, next) => next());
|
||||
const command2: Command2 = vi.fn((_ctx, _next) => {});
|
||||
|
||||
const [success1] = commandManager.chain().command1().run();
|
||||
const [success2] = commandManager.chain().command2().run();
|
||||
const [success1] = commandManager.chain().pipe(command1, {}).run();
|
||||
const [success2] = commandManager
|
||||
.chain()
|
||||
.pipe(command1, {})
|
||||
.pipe(command2)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
expect(command2).toHaveBeenCalled();
|
||||
@@ -62,15 +46,11 @@ describe('CommandManager', () => {
|
||||
const command2: Command = vi.fn((_ctx, next) => next());
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.command2()
|
||||
.command3()
|
||||
.pipe(command1)
|
||||
.pipe(command2)
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -84,15 +64,11 @@ describe('CommandManager', () => {
|
||||
const command2: Command = vi.fn((_ctx, _next) => {});
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.command2()
|
||||
.command3()
|
||||
.pipe(command1)
|
||||
.pipe(command2)
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -110,15 +86,11 @@ describe('CommandManager', () => {
|
||||
});
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.command2()
|
||||
.command3()
|
||||
.pipe(command1)
|
||||
.pipe(command2)
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -129,13 +101,11 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('can pass data to command when calling a command', () => {
|
||||
const command1: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
const command1: Command1 = vi.fn((_ctx, next) => next());
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1({ command1Option: 'test' })
|
||||
.pipe(command1, { command1Option: 'test' })
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalledWith(
|
||||
@@ -146,14 +116,14 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('can add data to the command chain with `with` method', () => {
|
||||
const command1: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
const command1: Command<{ commandData1: string }> = vi.fn((_ctx, next) =>
|
||||
next()
|
||||
);
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.with({ commandData1: 'test' })
|
||||
.command1()
|
||||
.pipe(command1)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalledWith(
|
||||
@@ -165,18 +135,19 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('passes and updates context across commands', () => {
|
||||
const command1: Command<'std', 'commandData1'> = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: '123' })
|
||||
const command1: Command<{}, { commandData1: string }> = vi.fn(
|
||||
(_ctx, next) => next({ commandData1: '123' })
|
||||
);
|
||||
const command2: Command<'commandData1'> = vi.fn((ctx, next) => {
|
||||
const command2: Command<{ commandData1: string }> = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('123');
|
||||
next({ commandData1: '456' });
|
||||
});
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
|
||||
const [success, ctx] = commandManager.chain().command1().command2().run();
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.pipe(command1)
|
||||
.pipe(command2)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
expect(command2).toHaveBeenCalled();
|
||||
@@ -184,57 +155,15 @@ describe('CommandManager', () => {
|
||||
expect(ctx.commandData1).toBe('456');
|
||||
});
|
||||
|
||||
test('can execute an inline command', () => {
|
||||
const inlineCommand: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
const success = commandManager.chain().inline(inlineCommand).run();
|
||||
|
||||
expect(inlineCommand).toHaveBeenCalled();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can execute a single command with `exec`', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: (_ctx.command1Option ?? '') + '123' })
|
||||
);
|
||||
const command2: Command2 = vi.fn((_ctx, next) =>
|
||||
next({ commandData2: 'cmd2' })
|
||||
);
|
||||
const command3: Command3 = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const result1 = commandManager.exec('command1');
|
||||
const result2 = commandManager.exec('command1', {
|
||||
command1Option: 'test',
|
||||
});
|
||||
const result3 = commandManager.exec('command2');
|
||||
const result4 = commandManager.exec('command3');
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
expect(command2).toHaveBeenCalled();
|
||||
expect(command3).toHaveBeenCalled();
|
||||
expect(result1).toEqual({ commandData1: '123', success: true });
|
||||
expect(result2).toEqual({ commandData1: 'test123', success: true });
|
||||
expect(result3).toEqual({ commandData2: 'cmd2', success: true });
|
||||
expect(result4).toEqual({ success: true });
|
||||
});
|
||||
|
||||
test('should not continue with the rest of the chain if all commands in `try` fail', () => {
|
||||
const command1: Command<never, 'commandData1'> = vi.fn((_ctx, _next) => {});
|
||||
const command1: Command = vi.fn((_ctx, _next) => {});
|
||||
const command2: Command = vi.fn((_ctx, _next) => {});
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.try(cmd => [cmd.command1(), cmd.command2()])
|
||||
.command3()
|
||||
.try(chain => [chain.pipe(command1), chain.pipe(command2)])
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -244,20 +173,16 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should not re-execute previous commands in the chain before `try`', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: '123' })
|
||||
const command1: Command<{}, { commandData1: string }> = vi.fn(
|
||||
(_ctx, next) => next({ commandData1: '123' })
|
||||
);
|
||||
const command2: Command = vi.fn((_ctx, _next) => {});
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.try(cmd => [cmd.command2(), cmd.command3()])
|
||||
.pipe(command1)
|
||||
.try(chain => [chain.pipe(command2), chain.pipe(command3)])
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalledTimes(1);
|
||||
@@ -268,22 +193,22 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should continue with the rest of the chain if one command in `try` succeeds', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, _next) => {});
|
||||
const command2: Command2 = vi.fn((_ctx, next) =>
|
||||
next({ commandData2: '123' })
|
||||
const command1: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string }
|
||||
> = vi.fn((_ctx, _next) => {});
|
||||
const command2: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData2: '123' }));
|
||||
const command3: Command<{}, { commandData3: string }> = vi.fn(
|
||||
(_ctx, next) => next({ commandData3: '456' })
|
||||
);
|
||||
const command3: Command3 = vi.fn((_ctx, next) =>
|
||||
next({ commandData3: '456' })
|
||||
);
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.try(cmd => [cmd.command1(), cmd.command2()])
|
||||
.command3()
|
||||
.try(chain => [chain.pipe(command1), chain.pipe(command2)])
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -296,19 +221,18 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should not execute any further commands in `try` after one succeeds', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: '123' })
|
||||
);
|
||||
const command2: Command2 = vi.fn((_ctx, next) =>
|
||||
next({ commandData2: '456' })
|
||||
);
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
const command1: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData1: '123' }));
|
||||
const command2: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData2: '456' }));
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.try(cmd => [cmd.command1(), cmd.command2()])
|
||||
.try(chain => [chain.pipe(command1), chain.pipe(command2)])
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -322,31 +246,23 @@ describe('CommandManager', () => {
|
||||
const command1: Command = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: 'fromCommand1', commandData2: 'fromCommand1' })
|
||||
);
|
||||
const command2: Command<'commandData1' | 'commandData2'> = vi.fn(
|
||||
(ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand1');
|
||||
expect(ctx.commandData2).toBe('fromCommand1');
|
||||
// override commandData2
|
||||
next({ commandData2: 'fromCommand2' });
|
||||
}
|
||||
);
|
||||
const command3: Command<'commandData1' | 'commandData2'> = vi.fn(
|
||||
(ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand1');
|
||||
expect(ctx.commandData2).toBe('fromCommand2');
|
||||
next();
|
||||
}
|
||||
);
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
const command2: Command = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand1');
|
||||
expect(ctx.commandData2).toBe('fromCommand1');
|
||||
// override commandData2
|
||||
next({ commandData2: 'fromCommand2' });
|
||||
});
|
||||
const command3: Command = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand1');
|
||||
expect(ctx.commandData2).toBe('fromCommand2');
|
||||
next();
|
||||
});
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.try(cmd => [cmd.command2()])
|
||||
.command3()
|
||||
.pipe(command1)
|
||||
.try(chain => [chain.pipe(command2)])
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -360,14 +276,10 @@ describe('CommandManager', () => {
|
||||
const command2: Command = vi.fn((_ctx, next) => next());
|
||||
const command3: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.tryAll(cmd => [cmd.command1(), cmd.command2()])
|
||||
.command3()
|
||||
.tryAll(chain => [chain.pipe(command1), chain.pipe(command2)])
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -377,23 +289,26 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should execute all commands in `tryAll` even if one has already succeeded', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: '123' })
|
||||
);
|
||||
const command2: Command2 = vi.fn((_ctx, next) =>
|
||||
next({ commandData2: '456' })
|
||||
);
|
||||
const command3: Command3 = vi.fn((_ctx, next) =>
|
||||
next({ commandData3: '789' })
|
||||
);
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
const command1: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string; commandData3?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData1: '123' }));
|
||||
const command2: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string; commandData3?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData2: '456' }));
|
||||
const command3: Command<
|
||||
{},
|
||||
{ commandData1?: string; commandData2?: string; commandData3?: string }
|
||||
> = vi.fn((_ctx, next) => next({ commandData3: '789' }));
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.tryAll(cmd => [cmd.command1(), cmd.command2(), cmd.command3()])
|
||||
.tryAll(chain => [
|
||||
chain.pipe(command1),
|
||||
chain.pipe(command2),
|
||||
chain.pipe(command3),
|
||||
])
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -406,20 +321,16 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should not continue with the rest of the chain if all commands in `tryAll` fail', () => {
|
||||
const command1: Command1 = vi.fn((_ctx, _next) => {});
|
||||
const command2: Command2 = vi.fn((_ctx, _next) => {});
|
||||
const command3: Command3 = vi.fn((_ctx, next) =>
|
||||
next({ commandData3: '123' })
|
||||
const command1: Command = vi.fn((_ctx, _next) => {});
|
||||
const command2: Command = vi.fn((_ctx, _next) => {});
|
||||
const command3: Command<{}, { commandData3: string }> = vi.fn(
|
||||
(_ctx, next) => next({ commandData3: '123' })
|
||||
);
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.tryAll(cmd => [cmd.command1(), cmd.command2()])
|
||||
.command3()
|
||||
.tryAll(chain => [chain.pipe(command1), chain.pipe(command2)])
|
||||
.pipe(command3)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -430,43 +341,44 @@ describe('CommandManager', () => {
|
||||
});
|
||||
|
||||
test('should pass context correctly in `tryAll` when at least one command succeeds', () => {
|
||||
const command1: Command = vi.fn((_ctx, next) =>
|
||||
next({ commandData1: 'fromCommand1' })
|
||||
const command1: Command<{}, { commandData1: string }> = vi.fn(
|
||||
(_ctx, next) => next({ commandData1: 'fromCommand1' })
|
||||
);
|
||||
const command2: Command<'commandData1'> = vi.fn((ctx, next) => {
|
||||
const command2: Command<
|
||||
{ commandData1: string; commandData2?: string },
|
||||
{ commandData2: string; commandData3: string }
|
||||
> = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand1');
|
||||
// override commandData1
|
||||
next({ commandData1: 'fromCommand2', commandData2: 'fromCommand2' });
|
||||
});
|
||||
const command3: Command<'commandData1' | 'commandData2'> = vi.fn(
|
||||
(ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand2');
|
||||
expect(ctx.commandData2).toBe('fromCommand2');
|
||||
next({
|
||||
// override commandData2
|
||||
commandData2: 'fromCommand3',
|
||||
commandData3: 'fromCommand3',
|
||||
});
|
||||
}
|
||||
);
|
||||
const command4: Command<'commandData1' | 'commandData2' | 'commandData3'> =
|
||||
vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand2');
|
||||
expect(ctx.commandData2).toBe('fromCommand3');
|
||||
expect(ctx.commandData3).toBe('fromCommand3');
|
||||
next();
|
||||
const command3: Command<
|
||||
{ commandData1: string; commandData2?: string },
|
||||
{ commandData2: string; commandData3: string }
|
||||
> = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand2');
|
||||
expect(ctx.commandData2).toBe('fromCommand2');
|
||||
next({
|
||||
// override commandData2
|
||||
commandData2: 'fromCommand3',
|
||||
commandData3: 'fromCommand3',
|
||||
});
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
commandManager.add('command4', command4);
|
||||
});
|
||||
const command4: Command<
|
||||
{ commandData1: string; commandData2: string; commandData3: string },
|
||||
{}
|
||||
> = vi.fn((ctx, next) => {
|
||||
expect(ctx.commandData1).toBe('fromCommand2');
|
||||
expect(ctx.commandData2).toBe('fromCommand3');
|
||||
expect(ctx.commandData3).toBe('fromCommand3');
|
||||
next();
|
||||
});
|
||||
|
||||
const [success, ctx] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.tryAll(cmd => [cmd.command2(), cmd.command3()])
|
||||
.command4()
|
||||
.pipe(command1)
|
||||
.tryAll(chain => [chain.pipe(command2), chain.pipe(command3)])
|
||||
.pipe(command4)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalled();
|
||||
@@ -485,16 +397,11 @@ describe('CommandManager', () => {
|
||||
const command3: Command = vi.fn((_ctx, _next) => {});
|
||||
const command4: Command = vi.fn((_ctx, next) => next());
|
||||
|
||||
commandManager.add('command1', command1);
|
||||
commandManager.add('command2', command2);
|
||||
commandManager.add('command3', command3);
|
||||
commandManager.add('command4', command4);
|
||||
|
||||
const [success] = commandManager
|
||||
.chain()
|
||||
.command1()
|
||||
.tryAll(cmd => [cmd.command2(), cmd.command3()])
|
||||
.command4()
|
||||
.pipe(command1)
|
||||
.tryAll(chain => [chain.pipe(command2), chain.pipe(command3)])
|
||||
.pipe(command4)
|
||||
.run();
|
||||
|
||||
expect(command1).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -4,77 +4,39 @@
|
||||
// type TestA = MakeOptionalIfEmpty<A>; // void | {}
|
||||
// type TestB = MakeOptionalIfEmpty<B>; // void | { prop?: string }
|
||||
// type TestC = MakeOptionalIfEmpty<C>; // { prop: string }
|
||||
import type { BlockStdScope } from '../scope/block-std-scope.js';
|
||||
import type { cmdSymbol } from './consts.js';
|
||||
|
||||
export type IfAllKeysOptional<T, Yes, No> =
|
||||
Partial<T> extends T ? (T extends Partial<T> ? Yes : No) : No;
|
||||
type MakeOptionalIfEmpty<T> = IfAllKeysOptional<T, void | T, T>;
|
||||
|
||||
export interface InitCommandCtx {
|
||||
std: BlockSuite.Std;
|
||||
std: BlockStdScope;
|
||||
}
|
||||
|
||||
export type CommandKeyToData<K extends BlockSuite.CommandDataName> = Pick<
|
||||
BlockSuite.CommandContext,
|
||||
K
|
||||
>;
|
||||
export type Command<
|
||||
In extends BlockSuite.CommandDataName = never,
|
||||
Out extends BlockSuite.CommandDataName = never,
|
||||
InData extends object = {},
|
||||
> = (
|
||||
ctx: CommandKeyToData<In> & InitCommandCtx & InData,
|
||||
next: (ctx?: CommandKeyToData<Out>) => void
|
||||
) => void;
|
||||
type Omit1<A, B> = [keyof Omit<A, keyof B>] extends [never]
|
||||
? void
|
||||
: Omit<A, keyof B>;
|
||||
export type InDataOfCommand<C> =
|
||||
C extends Command<infer K, any, infer R> ? CommandKeyToData<K> & R : never;
|
||||
type OutDataOfCommand<C> =
|
||||
C extends Command<any, infer K, any> ? CommandKeyToData<K> : never;
|
||||
|
||||
type CommonMethods<In extends object = {}> = {
|
||||
inline: <InlineOut extends BlockSuite.CommandDataName = never>(
|
||||
command: Command<Extract<keyof In, BlockSuite.CommandDataName>, InlineOut>
|
||||
) => Chain<In & CommandKeyToData<InlineOut>>;
|
||||
try: <InlineOut extends BlockSuite.CommandDataName = never>(
|
||||
fn: (chain: Chain<In>) => Chain<In & CommandKeyToData<InlineOut>>[]
|
||||
) => Chain<In & CommandKeyToData<InlineOut>>;
|
||||
tryAll: <InlineOut extends BlockSuite.CommandDataName = never>(
|
||||
fn: (chain: Chain<In>) => Chain<In & CommandKeyToData<InlineOut>>[]
|
||||
) => Chain<In & CommandKeyToData<InlineOut>>;
|
||||
run(): [
|
||||
result: boolean,
|
||||
ctx: CommandKeyToData<Extract<keyof In, BlockSuite.CommandDataName>>,
|
||||
];
|
||||
with<T extends Partial<BlockSuite.CommandContext>>(value: T): Chain<In & T>;
|
||||
};
|
||||
|
||||
type Cmds = {
|
||||
export type Cmds = {
|
||||
[cmdSymbol]: Command[];
|
||||
};
|
||||
|
||||
export type Chain<In extends object = {}> = CommonMethods<In> & {
|
||||
[K in keyof BlockSuite.Commands]: (
|
||||
data: MakeOptionalIfEmpty<
|
||||
Omit1<InDataOfCommand<BlockSuite.Commands[K]>, In>
|
||||
>
|
||||
) => Chain<In & OutDataOfCommand<BlockSuite.Commands[K]>>;
|
||||
} & Cmds;
|
||||
export type Command<Input = InitCommandCtx, Output = {}> = (
|
||||
input: Input & InitCommandCtx,
|
||||
next: (output?: Output) => void
|
||||
) => void;
|
||||
|
||||
export type ExecCommandResult<K extends keyof BlockSuite.Commands> =
|
||||
OutDataOfCommand<BlockSuite.Commands[K]>;
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface CommandContext extends InitCommandCtx {}
|
||||
|
||||
interface Commands {}
|
||||
|
||||
type CommandName = keyof Commands;
|
||||
type CommandDataName = keyof CommandContext;
|
||||
|
||||
type CommandChain<In extends object = {}> = Chain<In & InitCommandCtx>;
|
||||
}
|
||||
}
|
||||
export type Chain<CommandCtx extends object = InitCommandCtx> = {
|
||||
[cmdSymbol]: Command[];
|
||||
with: <Out extends object>(input: Out) => Chain<CommandCtx & Out>;
|
||||
pipe: {
|
||||
<Out extends object>(
|
||||
command: Command<CommandCtx, Out>
|
||||
): Chain<CommandCtx & Out>;
|
||||
<Out extends object, In extends object>(
|
||||
command: Command<In, Out>,
|
||||
input?: In
|
||||
): Chain<CommandCtx & In & Out>;
|
||||
};
|
||||
try: <Out extends object>(
|
||||
commands: (chain: Chain<CommandCtx>) => Chain<CommandCtx & Out>[]
|
||||
) => Chain<CommandCtx & Out>;
|
||||
tryAll: <Out extends object>(
|
||||
commands: (chain: Chain<CommandCtx>) => Chain<CommandCtx & Out>[]
|
||||
) => Chain<CommandCtx & Out>;
|
||||
run: () => [false, Partial<CommandCtx> & InitCommandCtx] | [true, CommandCtx];
|
||||
};
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { CommandIdentifier } from '../identifier.js';
|
||||
import type { BlockCommands } from '../spec/index.js';
|
||||
|
||||
/**
|
||||
* Create a command extension.
|
||||
*
|
||||
* @param commands A map of command names to command implementations.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { CommandExtension } from '@blocksuite/block-std';
|
||||
*
|
||||
* const MyCommandExtension = CommandExtension({
|
||||
* 'my-command': MyCommand
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function CommandExtension(commands: BlockCommands): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
Object.entries(commands).forEach(([name, command]) => {
|
||||
di.addImpl(CommandIdentifier(name), () => command);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './block-view.js';
|
||||
export * from './command.js';
|
||||
export * from './config.js';
|
||||
export * from './dnd/index.js';
|
||||
export * from './flavour.js';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
import type { StaticValue } from 'lit/static-html.js';
|
||||
|
||||
export type BlockCommands = Partial<BlockSuite.Commands>;
|
||||
export type BlockViewType = StaticValue | ((model: BlockModel) => StaticValue);
|
||||
export type WidgetViewMapType = Record<string, StaticValue>;
|
||||
|
||||
Reference in New Issue
Block a user