mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user