chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions
@@ -0,0 +1,32 @@
import { BlockViewIdentifier } from '../identifier.js';
import type { BlockViewType } from '../spec/type.js';
import type { ExtensionType } from './extension.js';
/**
* Create a block view extension.
*
* @param flavour The flavour of the block that the view is for.
* @param view Lit literal template for the view. Example: `my-list-block`
*
* The view is a lit template that is used to render the block.
*
* @example
* ```ts
* import { BlockViewExtension } from '@blocksuite/block-std';
*
* const MyListBlockViewExtension = BlockViewExtension(
* 'affine:list',
* literal`my-list-block`
* );
* ```
*/
export function BlockViewExtension(
flavour: BlockSuite.Flavour,
view: BlockViewType
): ExtensionType {
return {
setup: di => {
di.addImpl(BlockViewIdentifier(flavour), () => view);
},
};
}
@@ -0,0 +1,27 @@
import { CommandIdentifier } from '../identifier.js';
import type { BlockCommands } from '../spec/index.js';
import type { ExtensionType } from './extension.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);
});
},
};
}
@@ -0,0 +1,30 @@
import { ConfigIdentifier } from '../identifier.js';
import type { ExtensionType } from './extension.js';
/**
* Create a config extension.
* A config extension provides a configuration object for a block flavour.
* The configuration object can be used like:
* ```ts
* const config = std.provider.get(ConfigIdentifier('my-flavour'));
* ```
*
* @param flavor The flavour of the block that the config is for.
* @param config The configuration object.
*
* @example
* ```ts
* import { ConfigExtension } from '@blocksuite/block-std';
* const MyConfigExtension = ConfigExtension('my-flavour', config);
* ```
*/
export function ConfigExtension(
flavor: BlockSuite.Flavour,
config: Record<string, unknown>
): ExtensionType {
return {
setup: di => {
di.addImpl(ConfigIdentifier(flavor), () => config);
},
};
}
@@ -0,0 +1,17 @@
import type { Container } from '@blocksuite/global/di';
/**
* Generic extension.
* Extensions are used to set up the dependency injection container.
* In most cases, you won't need to use this class directly.
* We provide helper classes like `CommandExtension` and `BlockViewExtension` to make it easier to create extensions.
*/
export abstract class Extension {
static setup(_di: Container): void {
// do nothing
}
}
export interface ExtensionType {
setup(di: Container): void;
}
@@ -0,0 +1,25 @@
import { BlockFlavourIdentifier } from '../identifier.js';
import type { ExtensionType } from './extension.js';
/**
* Create a flavour extension.
*
* @param flavour
* The flavour of the block that the extension is for.
*
* @example
* ```ts
* import { FlavourExtension } from '@blocksuite/block-std';
*
* const MyFlavourExtension = FlavourExtension('my-flavour');
* ```
*/
export function FlavourExtension(flavour: string): ExtensionType {
return {
setup: di => {
di.addImpl(BlockFlavourIdentifier(flavour), () => ({
flavour,
}));
},
};
}
@@ -0,0 +1,11 @@
export * from './block-view.js';
export * from './command.js';
export * from './config.js';
export * from './extension.js';
export * from './flavour.js';
export * from './keymap.js';
export * from './lifecycle-watcher.js';
export * from './selection.js';
export * from './service.js';
export * from './service-watcher.js';
export * from './widget-view-map.js';
@@ -0,0 +1,48 @@
import type { EventOptions, UIEventHandler } from '../event/index.js';
import { KeymapIdentifier } from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import type { ExtensionType } from './extension.js';
let id = 1;
/**
* Create a keymap extension.
*
* @param keymapFactory
* Create keymap of the extension.
* It should return an object with `keymap` and `options`.
*
* `keymap` is a record of keymap.
*
* @param options
* `options` is an optional object that restricts the event to be handled.
*
* @example
* ```ts
* import { KeymapExtension } from '@blocksuite/block-std';
*
* const MyKeymapExtension = KeymapExtension(std => {
* return {
* keymap: {
* 'mod-a': SelectAll
* }
* options: {
* flavour: 'affine:paragraph'
* }
* }
* });
* ```
*/
export function KeymapExtension(
keymapFactory: (std: BlockStdScope) => Record<string, UIEventHandler>,
options?: EventOptions
): ExtensionType {
return {
setup: di => {
di.addImpl(KeymapIdentifier(`Keymap-${id++}`), {
getter: keymapFactory,
options,
});
},
};
}
@@ -0,0 +1,70 @@
import type { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { LifeCycleWatcherIdentifier, StdIdentifier } from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import { Extension } from './extension.js';
/**
* A life cycle watcher is an extension that watches the life cycle of the editor.
* It is used to perform actions when the editor is created, mounted, rendered, or unmounted.
*
* When creating a life cycle watcher, you must define a key that is unique to the watcher.
* The key is used to identify the watcher in the dependency injection container.
* ```ts
* class MyLifeCycleWatcher extends LifeCycleWatcher {
* static override readonly key = 'my-life-cycle-watcher';
* ```
*
* In the life cycle watcher, the methods will be called in the following order:
* 1. `created`: Called when the std is created.
* 2. `rendered`: Called when `std.render` is called.
* 3. `mounted`: Called when the editor host is mounted.
* 4. `unmounted`: Called when the editor host is unmounted.
*/
export abstract class LifeCycleWatcher extends Extension {
static key: string;
constructor(readonly std: BlockStdScope) {
super();
}
static override setup(di: Container) {
if (!this.key) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Key is not defined in the LifeCycleWatcher'
);
}
di.add(this as unknown as { new (std: BlockStdScope): LifeCycleWatcher }, [
StdIdentifier,
]);
di.addImpl(LifeCycleWatcherIdentifier(this.key), provider =>
provider.get(this)
);
}
/**
* Called when std is created.
*/
created() {}
/**
* Called when editor host is mounted.
* Which means the editor host emit the `connectedCallback` lifecycle event.
*/
mounted() {}
/**
* Called when `std.render` is called.
*/
rendered() {}
/**
* Called when editor host is unmounted.
* Which means the editor host emit the `disconnectedCallback` lifecycle event.
*/
unmounted() {}
}
@@ -0,0 +1,13 @@
import { SelectionIdentifier } from '../identifier.js';
import type { SelectionConstructor } from '../selection/index.js';
import type { ExtensionType } from './extension.js';
export function SelectionExtension(
selectionCtor: SelectionConstructor
): ExtensionType {
return {
setup: di => {
di.addImpl(SelectionIdentifier(selectionCtor.type), () => selectionCtor);
},
};
}
@@ -0,0 +1,47 @@
import type { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import {
BlockServiceIdentifier,
LifeCycleWatcherIdentifier,
StdIdentifier,
} from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import { LifeCycleWatcher } from './lifecycle-watcher.js';
import type { BlockService } from './service.js';
const idMap = new Map<string, number>();
/**
* @deprecated
* BlockServiceWatcher is deprecated. You should reconsider where to put your feature.
*
* BlockServiceWatcher is a legacy extension that is used to watch the slots registered on block service.
* However, we recommend using the new extension system.
*/
export abstract class BlockServiceWatcher extends LifeCycleWatcher {
static flavour: string;
constructor(
std: BlockStdScope,
readonly blockService: BlockService
) {
super(std);
}
static override setup(di: Container) {
if (!this.flavour) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Flavour is not defined in the BlockServiceWatcher'
);
}
const id = idMap.get(this.flavour) ?? 0;
idMap.set(this.flavour, id + 1);
di.addImpl(
LifeCycleWatcherIdentifier(`${this.flavour}-watcher-${id}`),
this,
[StdIdentifier, BlockServiceIdentifier(this.flavour)]
);
}
}
@@ -0,0 +1,120 @@
import type { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { EventName, UIEventHandler } from '../event/index.js';
import {
BlockFlavourIdentifier,
BlockServiceIdentifier,
StdIdentifier,
} from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import { getSlots } from '../spec/index.js';
import { Extension } from './extension.js';
/**
* @deprecated
* BlockService is deprecated. You should reconsider where to put your feature.
*
* BlockService is a legacy extension that is used to provide services to the block.
* In the previous version of BlockSuite, block service provides a way to extend the block.
* However, in the new version, we recommend using the new extension system.
*/
export abstract class BlockService extends Extension {
static flavour: string;
readonly disposables = new DisposableGroup();
readonly flavour: string;
readonly specSlots = getSlots();
get collection() {
return this.std.collection;
}
get doc() {
return this.std.doc;
}
get host() {
return this.std.host;
}
get selectionManager() {
return this.std.selection;
}
get uiEventDispatcher() {
return this.std.event;
}
constructor(
readonly std: BlockStdScope,
readonly flavourProvider: { flavour: string }
) {
super();
this.flavour = flavourProvider.flavour;
}
static override setup(di: Container) {
if (!this.flavour) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Flavour is not defined in the BlockService'
);
}
di.add(
this as unknown as {
new (
std: BlockStdScope,
flavourProvider: { flavour: string }
): BlockService;
},
[StdIdentifier, BlockFlavourIdentifier(this.flavour)]
);
di.addImpl(BlockServiceIdentifier(this.flavour), provider =>
provider.get(this)
);
}
bindHotKey(
keymap: Record<string, UIEventHandler>,
options?: { global: boolean }
) {
this.disposables.add(
this.uiEventDispatcher.bindHotkey(keymap, {
flavour: options?.global ? undefined : this.flavour,
})
);
}
// life cycle start
dispose() {
this.disposables.dispose();
}
// event handlers start
handleEvent(
name: EventName,
fn: UIEventHandler,
options?: { global: boolean }
) {
this.disposables.add(
this.uiEventDispatcher.add(name, fn, {
flavour: options?.global ? undefined : this.flavour,
})
);
}
// life cycle end
mounted() {
this.specSlots.mounted.emit({ service: this });
}
unmounted() {
this.dispose();
this.specSlots.unmounted.emit({ service: this });
}
// event handlers end
}
@@ -0,0 +1,31 @@
import { WidgetViewMapIdentifier } from '../identifier.js';
import type { WidgetViewMapType } from '../spec/type.js';
import type { ExtensionType } from './extension.js';
/**
* Create a widget view map extension.
*
* @param flavour The flavour of the block that the widget view map is for.
* @param widgetViewMap A map of widget names to widget view lit literal.
*
* A widget view map is to provide a map of widgets to a block.
* For every target block, it's view will be rendered with the widget views.
*
* @example
* ```ts
* import { WidgetViewMapExtension } from '@blocksuite/block-std';
*
* const MyWidgetViewMapExtension = WidgetViewMapExtension('my-flavour', {
* 'my-widget': literal`my-widget-view`
* });
*/
export function WidgetViewMapExtension(
flavour: BlockSuite.Flavour,
widgetViewMap: WidgetViewMapType
): ExtensionType {
return {
setup: di => {
di.addImpl(WidgetViewMapIdentifier(flavour), () => widgetViewMap);
},
};
}