mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 07:36:42 +08:00
@@ -0,0 +1,33 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { BlockViewIdentifier } from '../identifier.js';
|
||||
import type { BlockViewType } from '../spec/type.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/std';
|
||||
*
|
||||
* const MyListBlockViewExtension = BlockViewExtension(
|
||||
* 'affine:list',
|
||||
* literal`my-list-block`
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function BlockViewExtension(
|
||||
flavour: string,
|
||||
view: BlockViewType
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(BlockViewIdentifier(flavour), () => view);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { ConfigIdentifier } from '../identifier.js';
|
||||
|
||||
export interface ConfigFactory<Config extends Record<string, any>> {
|
||||
(config: Config): ExtensionType;
|
||||
identifier: ServiceIdentifier<Config>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getOptional(ConfigIdentifier('my-flavour'));
|
||||
* ```
|
||||
*
|
||||
* @param configId The id of the config. Should be unique for each config.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { ConfigExtensionFactory } from '@blocksuite/std';
|
||||
* const MyConfigExtensionFactory = ConfigExtensionFactory<ConfigType>('my-flavour');
|
||||
* const MyConfigExtension = MyConfigExtensionFactory({
|
||||
* option1: 'value1',
|
||||
* option2: 'value2',
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function ConfigExtensionFactory<Config extends Record<string, any>>(
|
||||
configId: string
|
||||
): ConfigFactory<Config> {
|
||||
const identifier = ConfigIdentifier(configId) as ServiceIdentifier<Config>;
|
||||
const extensionFactory = (config: Config): ExtensionType => ({
|
||||
setup: di => {
|
||||
di.override(ConfigIdentifier(configId), () => {
|
||||
return config;
|
||||
});
|
||||
},
|
||||
});
|
||||
extensionFactory.identifier = identifier;
|
||||
return extensionFactory;
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
import {
|
||||
draggable,
|
||||
dropTargetForElements,
|
||||
type ElementGetFeedbackArgs,
|
||||
monitorForElements,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import { centerUnderPointer } from '@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer';
|
||||
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
|
||||
import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
|
||||
import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
|
||||
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
||||
import type { DropTargetRecord } from '@atlaskit/pragmatic-drag-and-drop/types';
|
||||
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
|
||||
import {
|
||||
attachClosestEdge,
|
||||
type Edge,
|
||||
extractClosestEdge,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import type { ServiceIdentifier } from '@blocksuite/global/di';
|
||||
|
||||
import { LifeCycleWatcherIdentifier } from '../../identifier.js';
|
||||
import { LifeCycleWatcher } from '../lifecycle-watcher.js';
|
||||
import type {
|
||||
ElementDragEventBaseArgs,
|
||||
ElementDragEventMap,
|
||||
ElementDropEventMap,
|
||||
ElementDropTargetFeedbackArgs,
|
||||
ElementMonitorFeedbackArgs,
|
||||
OriginalAutoScrollOption,
|
||||
OriginalDraggableOption,
|
||||
OriginalDropTargetOption,
|
||||
OriginalMonitorOption,
|
||||
} from './types.js';
|
||||
|
||||
export type DragEntity = { type: string };
|
||||
|
||||
export type DragFrom = { at: string };
|
||||
|
||||
export type DragFromBlockSuite = {
|
||||
at: 'blocksuite-editor';
|
||||
docId: string;
|
||||
};
|
||||
|
||||
export type DragPayload<
|
||||
E extends DragEntity = DragEntity,
|
||||
F extends DragFrom = DragFromBlockSuite,
|
||||
> = {
|
||||
bsEntity?: E;
|
||||
from?: F;
|
||||
};
|
||||
|
||||
export type DropPayload<T extends {} = {}> = {
|
||||
edge?: Edge;
|
||||
} & T;
|
||||
|
||||
export type DropEdge = Edge;
|
||||
|
||||
export interface DNDEntity {
|
||||
basic: DragEntity;
|
||||
}
|
||||
|
||||
export type DraggableOption<
|
||||
PayloadEntity extends DragEntity,
|
||||
PayloadFrom extends DragFrom,
|
||||
DropPayload extends {},
|
||||
> = Pick<OriginalDraggableOption, 'element' | 'dragHandle' | 'canDrag'> & {
|
||||
/**
|
||||
* Set drag data for the draggable element.
|
||||
* @see {@link ElementGetFeedbackArgs} for callback arguments
|
||||
* @param callback - callback to set drag
|
||||
*/
|
||||
setDragData?: (args: ElementGetFeedbackArgs) => PayloadEntity;
|
||||
|
||||
/**
|
||||
* Set external drag data for the draggable element.
|
||||
* @param callback - callback to set external drag data
|
||||
* @see {@link ElementGetFeedbackArgs} for callback arguments
|
||||
*/
|
||||
setExternalDragData?: (
|
||||
args: ElementGetFeedbackArgs
|
||||
) => ReturnType<
|
||||
Required<OriginalDraggableOption>['getInitialDataForExternal']
|
||||
>;
|
||||
|
||||
/**
|
||||
* Set custom drag preview for the draggable element.
|
||||
*
|
||||
* `setDragPreview` is a function that will be called with a `container` element and other drag data as parameter when the drag preview is generating.
|
||||
* Append the custom element to the `container` which will be used to generate the preview. Once the drag preview is generated, the
|
||||
* `container` element and its children will be removed automatically.
|
||||
*
|
||||
* If you want to completely disable the drag preview, just set `setDragPreview` to `false`.
|
||||
*
|
||||
* @example
|
||||
* dnd.draggable({
|
||||
* // ...
|
||||
* setDragPreview: ({ container }) => {
|
||||
* const preview = document.createElement('div');
|
||||
* preview.style.width = '100px';
|
||||
* preview.style.height = '100px';
|
||||
* preview.style.backgroundColor = 'red';
|
||||
* preview.innerText = 'Custom Drag Preview';
|
||||
* container.appendChild(preview);
|
||||
*
|
||||
* return () => {
|
||||
* // do some cleanup
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @param callback - callback to set custom drag preview
|
||||
* @returns
|
||||
*/
|
||||
setDragPreview?:
|
||||
| false
|
||||
| ((
|
||||
options: ElementDragEventBaseArgs<
|
||||
DragPayload<PayloadEntity, PayloadFrom>
|
||||
> & {
|
||||
/**
|
||||
* Allows you to use the native `setDragImage` function if you want
|
||||
* Although, we recommend using alternative techniques (see element adapter docs)
|
||||
*/
|
||||
nativeSetDragImage: DataTransfer['setDragImage'] | null;
|
||||
container: HTMLElement;
|
||||
setOffset: (
|
||||
offset: 'preserve' | 'center' | { x: number; y: number }
|
||||
) => void;
|
||||
}
|
||||
) => void | (() => void));
|
||||
} & ElementDragEventMap<DragPayload<PayloadEntity, PayloadFrom>, DropPayload>;
|
||||
|
||||
export type DropTargetOption<
|
||||
PayloadEntity extends DragEntity,
|
||||
PayloadFrom extends DragFrom,
|
||||
DropPayload extends {},
|
||||
> = {
|
||||
/**
|
||||
* {@link OriginalDropTargetOption.element}
|
||||
*/
|
||||
element: HTMLElement;
|
||||
|
||||
/**
|
||||
* Allow drop position for the drop target.
|
||||
*/
|
||||
allowDropPosition?: Edge[];
|
||||
|
||||
/**
|
||||
* {@link OriginalDropTargetOption.getDropEffect}
|
||||
*/
|
||||
getDropEffect?: (
|
||||
args: ElementDropTargetFeedbackArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => DropTargetRecord['dropEffect'];
|
||||
|
||||
/**
|
||||
* {@link OriginalDropTargetOption.canDrop}
|
||||
*/
|
||||
canDrop?: (
|
||||
args: ElementDropTargetFeedbackArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* {@link OriginalDropTargetOption.getData}
|
||||
*/
|
||||
setDropData?: (
|
||||
args: ElementDropTargetFeedbackArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => DropPayload;
|
||||
|
||||
/**
|
||||
* {@link OriginalDropTargetOption.getIsSticky}
|
||||
*/
|
||||
getIsSticky?: (
|
||||
args: ElementDropTargetFeedbackArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => boolean;
|
||||
} & ElementDropEventMap<DragPayload<PayloadEntity, PayloadFrom>, DropPayload>;
|
||||
|
||||
export type MonitorOption<
|
||||
PayloadEntity extends DragEntity,
|
||||
PayloadFrom extends DragFrom,
|
||||
DropPayload extends {},
|
||||
> = {
|
||||
/**
|
||||
* {@link OriginalMonitorOption.canMonitor}
|
||||
*/
|
||||
canMonitor?: (
|
||||
args: ElementMonitorFeedbackArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => boolean;
|
||||
} & ElementDragEventMap<DragPayload<PayloadEntity, PayloadFrom>, DropPayload>;
|
||||
|
||||
export type AutoScroll<
|
||||
PayloadEntity extends DragEntity,
|
||||
PayloadFrom extends DragFrom,
|
||||
> = {
|
||||
element: HTMLElement;
|
||||
canScroll?: (
|
||||
args: ElementDragEventBaseArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => void;
|
||||
getAllowedAxis?: (
|
||||
args: ElementDragEventBaseArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => ReturnType<Required<OriginalAutoScrollOption>['getAllowedAxis']>;
|
||||
getConfiguration?: (
|
||||
args: ElementDragEventBaseArgs<DragPayload<PayloadEntity, PayloadFrom>>
|
||||
) => ReturnType<Required<OriginalAutoScrollOption>['getConfiguration']>;
|
||||
};
|
||||
|
||||
export const DndExtensionIdentifier = LifeCycleWatcherIdentifier(
|
||||
'DndController'
|
||||
) as ServiceIdentifier<DndController>;
|
||||
|
||||
export class DndController extends LifeCycleWatcher {
|
||||
static override key = 'DndController';
|
||||
|
||||
/**
|
||||
* Make an element draggable.
|
||||
*/
|
||||
draggable<
|
||||
PayloadEntity extends DragEntity = DragEntity,
|
||||
DropData extends {} = {},
|
||||
>(
|
||||
args: DraggableOption<
|
||||
PayloadEntity,
|
||||
DragFromBlockSuite,
|
||||
DropPayload<DropData>
|
||||
>
|
||||
) {
|
||||
const {
|
||||
setDragData,
|
||||
setExternalDragData,
|
||||
setDragPreview,
|
||||
element,
|
||||
dragHandle,
|
||||
...rest
|
||||
} = args;
|
||||
|
||||
return draggable({
|
||||
...(rest as Partial<OriginalDraggableOption>),
|
||||
element,
|
||||
dragHandle,
|
||||
onGenerateDragPreview: options => {
|
||||
if (setDragPreview) {
|
||||
let state: typeof centerUnderPointer | { x: number; y: number };
|
||||
|
||||
const setOffset = (
|
||||
offset: 'preserve' | 'center' | { x: number; y: number }
|
||||
) => {
|
||||
if (offset === 'center') {
|
||||
state = centerUnderPointer;
|
||||
} else if (offset === 'preserve') {
|
||||
state = preserveOffsetOnSource({
|
||||
element: options.source.element,
|
||||
input: options.location.current.input,
|
||||
});
|
||||
} else if (typeof offset === 'object') {
|
||||
if (
|
||||
offset.x < 0 ||
|
||||
offset.y < 0 ||
|
||||
typeof offset.x === 'string' ||
|
||||
typeof offset.y === 'string'
|
||||
) {
|
||||
state = pointerOutsideOfPreview({
|
||||
x:
|
||||
typeof offset.x === 'number'
|
||||
? `${Math.abs(offset.x)}px`
|
||||
: offset.x,
|
||||
y:
|
||||
typeof offset.y === 'number'
|
||||
? `${Math.abs(offset.y)}px`
|
||||
: offset.y,
|
||||
});
|
||||
}
|
||||
state = offset;
|
||||
}
|
||||
};
|
||||
|
||||
setCustomNativeDragPreview({
|
||||
getOffset: (...args) => {
|
||||
if (!state) {
|
||||
setOffset('center');
|
||||
}
|
||||
|
||||
if (typeof state === 'function') {
|
||||
return state(...args);
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
render: renderOption => {
|
||||
setDragPreview({
|
||||
setOffset,
|
||||
...options,
|
||||
...renderOption,
|
||||
});
|
||||
},
|
||||
nativeSetDragImage: options.nativeSetDragImage,
|
||||
});
|
||||
} else if (setDragPreview === false) {
|
||||
disableNativeDragPreview({
|
||||
nativeSetDragImage: options.nativeSetDragImage,
|
||||
});
|
||||
}
|
||||
},
|
||||
getInitialData: options => {
|
||||
const bsEntity = setDragData?.(options) ?? {};
|
||||
|
||||
return {
|
||||
bsEntity,
|
||||
from: {
|
||||
at: 'blocksuite-editor',
|
||||
docId: this.std.store.doc.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
getInitialDataForExternal: setExternalDragData
|
||||
? options => {
|
||||
return setExternalDragData?.(options);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an element a drop target.
|
||||
*/
|
||||
dropTarget<
|
||||
PayloadEntity extends DragEntity = DragEntity,
|
||||
DropData extends {} = {},
|
||||
PayloadFrom extends DragFrom = DragFromBlockSuite,
|
||||
>(args: DropTargetOption<PayloadEntity, PayloadFrom, DropPayload<DropData>>) {
|
||||
const {
|
||||
element,
|
||||
setDropData,
|
||||
allowDropPosition = ['bottom', 'left', 'top', 'right'],
|
||||
...rest
|
||||
} = args;
|
||||
|
||||
return dropTargetForElements({
|
||||
element,
|
||||
getData: options => {
|
||||
const data = setDropData?.(options) ?? {};
|
||||
const edge = extractClosestEdge(
|
||||
attachClosestEdge(data, {
|
||||
element: options.element,
|
||||
input: options.input,
|
||||
allowedEdges: allowDropPosition,
|
||||
})
|
||||
);
|
||||
|
||||
return edge
|
||||
? {
|
||||
...data,
|
||||
edge,
|
||||
}
|
||||
: data;
|
||||
},
|
||||
...(rest as Partial<OriginalDropTargetOption>),
|
||||
});
|
||||
}
|
||||
|
||||
monitor<
|
||||
PayloadEntity extends DragEntity = DragEntity,
|
||||
DropData extends {} = {},
|
||||
PayloadFrom extends DragFrom = DragFromBlockSuite,
|
||||
>(args: MonitorOption<PayloadEntity, PayloadFrom, DropPayload<DropData>>) {
|
||||
return monitorForElements(args as OriginalMonitorOption);
|
||||
}
|
||||
|
||||
autoScroll<
|
||||
PayloadEntity extends DragEntity = DragEntity,
|
||||
PayloadFrom extends DragFrom = DragFromBlockSuite,
|
||||
>(options: AutoScroll<PayloadEntity, PayloadFrom>) {
|
||||
return autoScrollForElements(options as OriginalAutoScrollOption);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type {
|
||||
draggable,
|
||||
dropTargetForElements,
|
||||
ElementDropTargetGetFeedbackArgs,
|
||||
ElementMonitorGetFeedbackArgs,
|
||||
monitorForElements,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import type {
|
||||
DragLocation,
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
DragLocationHistory,
|
||||
DropTargetRecord,
|
||||
ElementDragType,
|
||||
} from '@atlaskit/pragmatic-drag-and-drop/types';
|
||||
import type { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
|
||||
|
||||
export type ElementDragEventBaseArgs<Payload, DropPayload = {}> = {
|
||||
/**
|
||||
* {@link DragLocationHistory} of the drag
|
||||
*/
|
||||
location: {
|
||||
/**
|
||||
* {@link DragLocationHistory.initial}
|
||||
*/
|
||||
initial: DragLocationWithPayload<DropPayload>;
|
||||
/**
|
||||
* {@link DragLocationHistory.current}
|
||||
*/
|
||||
current: DragLocationWithPayload<DropPayload>;
|
||||
/**
|
||||
* {@link DragLocationHistory.previous}
|
||||
*/
|
||||
previous: Pick<DragLocationWithPayload<DropPayload>, 'dropTargets'>;
|
||||
};
|
||||
source: Omit<ElementDragType['payload'], 'data'> & { data: Payload };
|
||||
};
|
||||
|
||||
export type DragLocationWithPayload<Payload> = Omit<
|
||||
DragLocation,
|
||||
'dropTargets'
|
||||
> & {
|
||||
dropTargets: DropTargetRecordWithPayload<Payload>[];
|
||||
};
|
||||
|
||||
export type DropTargetRecordWithPayload<Payload> = Omit<
|
||||
DropTargetRecord,
|
||||
'data'
|
||||
> & {
|
||||
data: Payload;
|
||||
};
|
||||
|
||||
export type ElementDragEventMap<DragPayload, DropPayload> = {
|
||||
onDragStart?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload>
|
||||
) => void;
|
||||
onDrag?: (data: ElementDragEventBaseArgs<DragPayload, DropPayload>) => void;
|
||||
onDrop?: (data: ElementDragEventBaseArgs<DragPayload, DropPayload>) => void;
|
||||
onDropTargetChange?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload>
|
||||
) => void;
|
||||
};
|
||||
|
||||
type DropTargetLocalizedData = {
|
||||
self: DropTargetRecord;
|
||||
};
|
||||
|
||||
export type ElementDropTargetFeedbackArgs<Payload> = Omit<
|
||||
ElementDropTargetGetFeedbackArgs,
|
||||
'source'
|
||||
> & {
|
||||
source: Omit<ElementDragType['payload'], 'data'> & { data: Payload };
|
||||
};
|
||||
|
||||
export type ElementDropEventMap<DragPayload, DropPayload> = {
|
||||
onDragStart?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
onDrag?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
onDrop?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
onDropTargetChange?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
onDragEnter?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
onDragLeave?: (
|
||||
data: ElementDragEventBaseArgs<DragPayload, DropPayload> &
|
||||
DropTargetLocalizedData
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type ElementMonitorFeedbackArgs<Payload> = Omit<
|
||||
ElementMonitorGetFeedbackArgs,
|
||||
'source'
|
||||
> & {
|
||||
source: Omit<ElementDragType['payload'], 'data'> & { data: Payload };
|
||||
};
|
||||
|
||||
export type OriginalDraggableOption = Parameters<typeof draggable>[0];
|
||||
|
||||
export type OriginalDropTargetOption = Parameters<
|
||||
typeof dropTargetForElements
|
||||
>[0];
|
||||
|
||||
export type OriginalMonitorOption = Parameters<typeof monitorForElements>[0];
|
||||
|
||||
export type OriginalAutoScrollOption = Parameters<
|
||||
typeof autoScrollForElements
|
||||
>[0];
|
||||
@@ -0,0 +1,49 @@
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import type { BlockStdScope } from '../scope/std-scope';
|
||||
import { LifeCycleWatcher } from './lifecycle-watcher';
|
||||
|
||||
export class EditorLifeCycleExtension extends LifeCycleWatcher {
|
||||
static override key = 'editor-life-cycle';
|
||||
|
||||
disposables = new DisposableGroup();
|
||||
|
||||
readonly slots = {
|
||||
created: new Subject<void>(),
|
||||
mounted: new Subject<void>(),
|
||||
rendered: new Subject<void>(),
|
||||
unmounted: new Subject<void>(),
|
||||
};
|
||||
|
||||
constructor(override readonly std: BlockStdScope) {
|
||||
super(std);
|
||||
|
||||
this.disposables.add(this.slots.created);
|
||||
this.disposables.add(this.slots.mounted);
|
||||
this.disposables.add(this.slots.rendered);
|
||||
this.disposables.add(this.slots.unmounted);
|
||||
}
|
||||
|
||||
override created() {
|
||||
super.created();
|
||||
this.slots.created.next();
|
||||
}
|
||||
|
||||
override mounted() {
|
||||
super.mounted();
|
||||
this.slots.mounted.next();
|
||||
}
|
||||
|
||||
override rendered() {
|
||||
super.rendered();
|
||||
this.slots.rendered.next();
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
super.unmounted();
|
||||
this.slots.unmounted.next();
|
||||
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { BlockFlavourIdentifier } from '../identifier.js';
|
||||
|
||||
/**
|
||||
* Create a flavour extension.
|
||||
*
|
||||
* @param flavour
|
||||
* The flavour of the block that the extension is for.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FlavourExtension } from '@blocksuite/std';
|
||||
*
|
||||
* const MyFlavourExtension = FlavourExtension('my-flavour');
|
||||
* ```
|
||||
*/
|
||||
export function FlavourExtension(flavour: string): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(BlockFlavourIdentifier(flavour), () => ({
|
||||
flavour,
|
||||
}));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export * from './block-view.js';
|
||||
export * from './config.js';
|
||||
export * from './dnd/index.js';
|
||||
export * from './editor-life-cycle.js';
|
||||
export * from './flavour.js';
|
||||
export * from './keymap.js';
|
||||
export * from './lifecycle-watcher.js';
|
||||
export * from './service.js';
|
||||
export * from './service-manager.js';
|
||||
export * from './widget-view-map.js';
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { EventOptions, UIEventHandler } from '../event/index.js';
|
||||
import { KeymapIdentifier } from '../identifier.js';
|
||||
import type { BlockStdScope } from '../scope/index.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/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 { Extension } from '@blocksuite/store';
|
||||
|
||||
import { LifeCycleWatcherIdentifier, StdIdentifier } from '../identifier.js';
|
||||
import type { BlockStdScope } from '../scope/index.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,22 @@
|
||||
import { LifeCycleWatcher } from '../extension/index.js';
|
||||
import { BlockServiceIdentifier } from '../identifier.js';
|
||||
|
||||
export class ServiceManager extends LifeCycleWatcher {
|
||||
static override readonly key = 'serviceManager';
|
||||
|
||||
override mounted() {
|
||||
super.mounted();
|
||||
|
||||
this.std.provider.getAll(BlockServiceIdentifier).forEach(service => {
|
||||
service.mounted();
|
||||
});
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
super.unmounted();
|
||||
|
||||
this.std.provider.getAll(BlockServiceIdentifier).forEach(service => {
|
||||
service.unmounted();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type { Container } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { EventName, UIEventHandler } from '../event/index.js';
|
||||
import {
|
||||
BlockFlavourIdentifier,
|
||||
BlockServiceIdentifier,
|
||||
StdIdentifier,
|
||||
} from '../identifier.js';
|
||||
import type { BlockStdScope } from '../scope/index.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;
|
||||
|
||||
get collection() {
|
||||
return this.std.workspace;
|
||||
}
|
||||
|
||||
get doc() {
|
||||
return this.std.store;
|
||||
}
|
||||
|
||||
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() {}
|
||||
|
||||
unmounted() {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { WidgetViewIdentifier } from '../identifier.js';
|
||||
import type { WidgetViewType } from '../spec/type.js';
|
||||
|
||||
/**
|
||||
* Create a widget view extension.
|
||||
*
|
||||
* @param flavour The flavour of the block that the widget view is for.
|
||||
* @param id The id of the widget view.
|
||||
* @param view The widget view lit literal.
|
||||
*
|
||||
* A widget view is to provide a widget view for a block.
|
||||
* For every target block, it's view will be rendered with the widget view.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { WidgetViewExtension } from '@blocksuite/std';
|
||||
*
|
||||
* const MyWidgetViewExtension = WidgetViewExtension('my-flavour', 'my-widget', literal`my-widget-view`);
|
||||
*/
|
||||
export function WidgetViewExtension(
|
||||
flavour: string,
|
||||
id: string,
|
||||
view: WidgetViewType
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
if (flavour.includes('|') || id.includes('|')) {
|
||||
console.error(`Register view failed:`);
|
||||
console.error(
|
||||
`flavour or id cannot include '|', flavour: ${flavour}, id: ${id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const key = `${flavour}|${id}`;
|
||||
di.addImpl(WidgetViewIdentifier(key), view);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user