diff --git a/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts b/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts
new file mode 100644
index 0000000000..5a877ba3c4
--- /dev/null
+++ b/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts
@@ -0,0 +1,44 @@
+import type { Rect } from '@blocksuite/global/utils';
+import { css, html, LitElement } from 'lit';
+import { property } from 'lit/decorators.js';
+import { styleMap } from 'lit/directives/style-map.js';
+
+export class DragIndicator extends LitElement {
+ static override styles = css`
+ .affine-drag-indicator {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: var(--affine-primary-color);
+ transition-property: width, height, transform;
+ transition-duration: 100ms;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-delay: 0s;
+ transform-origin: 0 0;
+ pointer-events: none;
+ z-index: 2;
+ }
+ `;
+
+ override render() {
+ if (!this.rect) {
+ return null;
+ }
+ const { left, top, width, height } = this.rect;
+ const style = styleMap({
+ width: `${width}px`,
+ height: `${height}px`,
+ transform: `translate(${left}px, ${top}px)`,
+ });
+ return html`
`;
+ }
+
+ @property({ attribute: false })
+ accessor rect: Rect | null = null;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'affine-drag-indicator': DragIndicator;
+ }
+}
diff --git a/blocksuite/blocks/src/_common/components/file-drop-manager.ts b/blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts
similarity index 50%
rename from blocksuite/blocks/src/_common/components/file-drop-manager.ts
rename to blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts
index de31ed5607..15e5bfd73c 100644
--- a/blocksuite/blocks/src/_common/components/file-drop-manager.ts
+++ b/blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts
@@ -1,4 +1,3 @@
-import type { DragIndicator } from '@blocksuite/affine-components/drag-indicator';
import {
calcDropTarget,
type DropResult,
@@ -6,12 +5,21 @@ import {
isInsidePageEditor,
matchFlavours,
} from '@blocksuite/affine-shared/utils';
-import type { BlockService, EditorHost } from '@blocksuite/block-std';
+import {
+ type BlockStdScope,
+ type EditorHost,
+ type ExtensionType,
+ LifeCycleWatcher,
+} from '@blocksuite/block-std';
+import { createIdentifier } from '@blocksuite/global/di';
import type { IVec } from '@blocksuite/global/utils';
-import { assertExists, Point } from '@blocksuite/global/utils';
+import { Point } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
+import type { DragIndicator } from './index.js';
+
export type onDropProps = {
+ std: BlockStdScope;
files: File[];
targetModel: BlockModel | null;
place: 'before' | 'after';
@@ -20,54 +28,32 @@ export type onDropProps = {
export type FileDropOptions = {
flavour: string;
- onDrop?: ({
- files,
- targetModel,
- place,
- point,
- }: onDropProps) => Promise | void;
+ onDrop?: (onDropProps: onDropProps) => boolean;
};
-export class FileDropManager {
- private static _dropResult: DropResult | null = null;
+export class FileDropExtension extends LifeCycleWatcher {
+ static override readonly key = 'FileDropExtension';
- private readonly _blockService: BlockService;
+ static dropResult: DropResult | null = null;
- private readonly _fileDropOptions: FileDropOptions;
+ static get indicator() {
+ let indicator = document.querySelector(
+ 'affine-drag-indicator'
+ );
- private readonly _indicator!: DragIndicator;
+ if (!indicator) {
+ indicator = document.createElement(
+ 'affine-drag-indicator'
+ ) as DragIndicator;
+ document.body.append(indicator);
+ }
- private readonly _onDrop = (event: DragEvent) => {
- this._indicator.rect = null;
-
- const { onDrop } = this._fileDropOptions;
- if (!onDrop) return;
-
- const dataTransfer = event.dataTransfer;
- if (!dataTransfer) return;
-
- const effectAllowed = dataTransfer.effectAllowed;
- if (effectAllowed === 'none') return;
-
- const droppedFiles = dataTransfer.files;
- if (!droppedFiles || !droppedFiles.length) return;
-
- event.preventDefault();
-
- const { targetModel, type: place } = this;
- const { x, y } = event;
-
- onDrop({
- files: [...droppedFiles],
- targetModel,
- place,
- point: [x, y],
- })?.catch(console.error);
- };
+ return indicator;
+ }
onDragLeave = () => {
- FileDropManager._dropResult = null;
- this._indicator.rect = null;
+ FileDropExtension.dropResult = null;
+ FileDropExtension.indicator.rect = null;
};
onDragOver = (event: DragEvent) => {
@@ -86,40 +72,32 @@ export class FileDropManager {
let result: DropResult | null = null;
if (element) {
const model = element.model;
- const parent = this.doc.getParent(model);
- if (!matchFlavours(parent, ['affine:surface'])) {
+ const parent = this.std.doc.getParent(model);
+ if (!matchFlavours(parent, ['affine:surface' as BlockSuite.Flavour])) {
result = calcDropTarget(point, model, element);
}
}
if (result) {
- FileDropManager._dropResult = result;
- this._indicator.rect = result.rect;
+ FileDropExtension.dropResult = result;
+ FileDropExtension.indicator.rect = result.rect;
} else {
- FileDropManager._dropResult = null;
- this._indicator.rect = null;
+ FileDropExtension.dropResult = null;
+ FileDropExtension.indicator.rect = null;
}
};
- get doc() {
- return this._blockService.doc;
- }
-
- get editorHost(): EditorHost {
- return this._blockService.std.host;
- }
-
get targetModel(): BlockModel | null {
- let targetModel = FileDropManager._dropResult?.modelState.model || null;
+ let targetModel = FileDropExtension.dropResult?.modelState.model || null;
if (!targetModel && isInsidePageEditor(this.editorHost)) {
const rootModel = this.doc.root;
- assertExists(rootModel);
+ if (!rootModel) return null;
let lastNote = rootModel.children[rootModel.children.length - 1];
if (!lastNote || !matchFlavours(lastNote, ['affine:note'])) {
const newNoteId = this.doc.addBlock('affine:note', {}, rootModel.id);
const newNote = this.doc.getBlockById(newNoteId);
- assertExists(newNote);
+ if (!newNote) return null;
lastNote = newNote;
}
@@ -134,40 +112,93 @@ export class FileDropManager {
0
);
const newParagraph = this.doc.getBlockById(newParagraphId);
- assertExists(newParagraph);
+ if (!newParagraph) return null;
targetModel = newParagraph;
}
}
return targetModel;
}
+ get doc() {
+ return this.std.doc;
+ }
+
+ get editorHost(): EditorHost {
+ return this.std.host;
+ }
+
get type(): 'before' | 'after' {
- return !FileDropManager._dropResult ||
- FileDropManager._dropResult.type !== 'before'
+ return !FileDropExtension.dropResult ||
+ FileDropExtension.dropResult.type !== 'before'
? 'after'
: 'before';
}
- constructor(blockService: BlockService, fileDropOptions: FileDropOptions) {
- this._blockService = blockService;
- this._fileDropOptions = fileDropOptions;
+ private readonly _onDrop = (event: DragEvent, options: FileDropOptions) => {
+ FileDropExtension.indicator.rect = null;
- this._indicator = document.querySelector(
- 'affine-drag-indicator'
- ) as DragIndicator;
- if (!this._indicator) {
- this._indicator = document.createElement(
- 'affine-drag-indicator'
- ) as DragIndicator;
- document.body.append(this._indicator);
- }
+ const { onDrop } = options;
+ if (!onDrop) return;
- if (fileDropOptions.onDrop) {
- this._blockService.disposables.addFromEvent(
- this._blockService.std.host,
- 'drop',
- this._onDrop
- );
+ const dataTransfer = event.dataTransfer;
+ if (!dataTransfer) return;
+
+ const effectAllowed = dataTransfer.effectAllowed;
+ if (effectAllowed === 'none') return;
+
+ const droppedFiles = dataTransfer.files;
+ if (!droppedFiles || !droppedFiles.length) return;
+
+ const { targetModel, type: place } = this;
+ const { x, y } = event;
+
+ const drop = onDrop({
+ std: this.std,
+ files: [...droppedFiles],
+ targetModel,
+ place,
+ point: [x, y],
+ });
+
+ if (drop) {
+ event.preventDefault();
}
+ return drop;
+ };
+
+ override mounted() {
+ super.mounted();
+ const std = this.std;
+
+ std.event.add('nativeDragOver', context => {
+ const event = context.get('dndState');
+ this.onDragOver(event.raw);
+ });
+ std.event.add('nativeDragLeave', () => {
+ this.onDragLeave();
+ });
+ std.provider.getAll(FileDropConfigExtensionIdentifier).forEach(options => {
+ if (options.onDrop) {
+ std.event.add('nativeDrop', context => {
+ const event = context.get('dndState');
+ return this._onDrop(event.raw, options);
+ });
+ }
+ });
}
}
+
+const FileDropConfigExtensionIdentifier = createIdentifier(
+ 'FileDropConfigExtension'
+);
+
+export const FileDropConfigExtension = (
+ options: FileDropOptions
+): ExtensionType => {
+ const identifier = FileDropConfigExtensionIdentifier(options.flavour);
+ return {
+ setup: di => {
+ di.addImpl(identifier, () => options);
+ },
+ };
+};
diff --git a/blocksuite/affine/components/src/drag-indicator/index.ts b/blocksuite/affine/components/src/drag-indicator/index.ts
index e71ec34685..db2f29909a 100644
--- a/blocksuite/affine/components/src/drag-indicator/index.ts
+++ b/blocksuite/affine/components/src/drag-indicator/index.ts
@@ -1,47 +1,12 @@
-import type { Rect } from '@blocksuite/global/utils';
-import { css, html, LitElement } from 'lit';
-import { property } from 'lit/decorators.js';
-import { styleMap } from 'lit/directives/style-map.js';
+import { DragIndicator } from './drag-indicator.js';
+export {
+ FileDropConfigExtension,
+ FileDropExtension,
+ type FileDropOptions,
+ type onDropProps,
+} from './file-drop-manager.js';
-export class DragIndicator extends LitElement {
- static override styles = css`
- .affine-drag-indicator {
- position: absolute;
- top: 0;
- left: 0;
- background: var(--affine-primary-color);
- transition-property: width, height, transform;
- transition-duration: 100ms;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-delay: 0s;
- transform-origin: 0 0;
- pointer-events: none;
- z-index: 2;
- }
- `;
-
- override render() {
- if (!this.rect) {
- return null;
- }
- const { left, top, width, height } = this.rect;
- const style = styleMap({
- width: `${width}px`,
- height: `${height}px`,
- transform: `translate(${left}px, ${top}px)`,
- });
- return html``;
- }
-
- @property({ attribute: false })
- accessor rect: Rect | null = null;
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'affine-drag-indicator': DragIndicator;
- }
-}
+export { DragIndicator };
export function effects() {
customElements.define('affine-drag-indicator', DragIndicator);
diff --git a/blocksuite/blocks/src/_common/components/index.ts b/blocksuite/blocks/src/_common/components/index.ts
index 51fd439976..19d52716ce 100644
--- a/blocksuite/blocks/src/_common/components/index.ts
+++ b/blocksuite/blocks/src/_common/components/index.ts
@@ -1,6 +1,5 @@
export * from './ai-item/index.js';
export * from './block-selection.js';
export * from './block-zero-width.js';
-export * from './file-drop-manager.js';
export * from './menu-divider.js';
export { scrollbarStyle } from './utils.js';
diff --git a/blocksuite/blocks/src/attachment-block/attachment-service.ts b/blocksuite/blocks/src/attachment-block/attachment-service.ts
index d4d7ec14ef..c40c2cf9f6 100644
--- a/blocksuite/blocks/src/attachment-block/attachment-service.ts
+++ b/blocksuite/blocks/src/attachment-block/attachment-service.ts
@@ -1,3 +1,4 @@
+import { FileDropConfigExtension } from '@blocksuite/affine-components/drag-indicator';
import { AttachmentBlockSchema } from '@blocksuite/affine-model';
import {
DragHandleConfigExtension,
@@ -13,65 +14,63 @@ import {
import { BlockService } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
-import {
- FileDropManager,
- type FileDropOptions,
-} from '../_common/components/file-drop-manager.js';
import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH } from '../_common/consts.js';
import { addAttachments } from '../root-block/edgeless/utils/common.js';
import type { AttachmentBlockComponent } from './attachment-block.js';
import { AttachmentEdgelessBlockComponent } from './attachment-edgeless-block.js';
import { addSiblingAttachmentBlocks } from './utils.js';
+// bytes.parse('2GB')
+const maxFileSize = 2147483648;
+
export class AttachmentBlockService extends BlockService {
static override readonly flavour = AttachmentBlockSchema.model.flavour;
- private readonly _fileDropOptions: FileDropOptions = {
- flavour: this.flavour,
- onDrop: async ({ files, targetModel, place, point }) => {
- if (!files.length) return false;
+ maxFileSize = maxFileSize;
+}
- // generic attachment block for all files except images
- const attachmentFiles = files.filter(
- file => !file.type.startsWith('image/')
- );
+export const AttachmentDropOption = FileDropConfigExtension({
+ flavour: AttachmentBlockSchema.model.flavour,
+ onDrop: ({ files, targetModel, place, point, std }) => {
+ // generic attachment block for all files except images
+ const attachmentFiles = files.filter(
+ file => !file.type.startsWith('image/')
+ );
- if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
- await addSiblingAttachmentBlocks(
- this.host,
- attachmentFiles,
- this.maxFileSize,
- targetModel,
- place
- );
- } else if (isInsideEdgelessEditor(this.host)) {
- const gfx = this.std.get(GfxControllerIdentifier);
- point = gfx.viewport.toViewCoordFromClientCoord(point);
- await addAttachments(this.std, attachmentFiles, point);
+ if (!attachmentFiles.length) return false;
- this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
- control: 'canvas:drop',
- page: 'whiteboard editor',
- module: 'toolbar',
- segment: 'toolbar',
- type: 'attachment',
- });
- }
+ if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
+ addSiblingAttachmentBlocks(
+ std.host,
+ attachmentFiles,
+ // TODO: use max file size from service
+ maxFileSize,
+ targetModel,
+ place
+ ).catch(console.error);
return true;
- },
- };
+ }
- fileDropManager!: FileDropManager;
+ if (isInsideEdgelessEditor(std.host)) {
+ const gfx = std.get(GfxControllerIdentifier);
+ point = gfx.viewport.toViewCoordFromClientCoord(point);
+ addAttachments(std, attachmentFiles, point).catch(console.error);
- maxFileSize = 10 * 1000 * 1000; // 10MB (default)
+ std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
+ control: 'canvas:drop',
+ page: 'whiteboard editor',
+ module: 'toolbar',
+ segment: 'toolbar',
+ type: 'attachment',
+ });
- override mounted(): void {
- super.mounted();
+ return true;
+ }
- this.fileDropManager = new FileDropManager(this, this._fileDropOptions);
- }
-}
+ return false;
+ },
+});
export const AttachmentDragHandleOption = DragHandleConfigExtension({
flavour: AttachmentBlockSchema.model.flavour,
diff --git a/blocksuite/blocks/src/attachment-block/attachment-spec.ts b/blocksuite/blocks/src/attachment-block/attachment-spec.ts
index c968a5b49f..c603dfa566 100644
--- a/blocksuite/blocks/src/attachment-block/attachment-spec.ts
+++ b/blocksuite/blocks/src/attachment-block/attachment-spec.ts
@@ -9,6 +9,7 @@ import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-htm
import {
AttachmentBlockService,
AttachmentDragHandleOption,
+ AttachmentDropOption,
} from './attachment-service.js';
import {
AttachmentEmbedConfigExtension,
@@ -23,6 +24,7 @@ export const AttachmentBlockSpec: ExtensionType[] = [
? literal`affine-edgeless-attachment`
: literal`affine-attachment`;
}),
+ AttachmentDropOption,
AttachmentDragHandleOption,
AttachmentEmbedConfigExtension(),
AttachmentEmbedService,
diff --git a/blocksuite/blocks/src/image-block/image-service.ts b/blocksuite/blocks/src/image-block/image-service.ts
index f4c8f5d67c..01a26515fc 100644
--- a/blocksuite/blocks/src/image-block/image-service.ts
+++ b/blocksuite/blocks/src/image-block/image-service.ts
@@ -1,3 +1,4 @@
+import { FileDropConfigExtension } from '@blocksuite/affine-components/drag-indicator';
import { ImageBlockSchema } from '@blocksuite/affine-model';
import {
DragHandleConfigExtension,
@@ -13,64 +14,60 @@ import {
import { BlockService } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
-import {
- FileDropManager,
- type FileDropOptions,
-} from '../_common/components/file-drop-manager.js';
import { setImageProxyMiddlewareURL } from '../_common/transformers/middlewares.js';
import { addImages } from '../root-block/edgeless/utils/common.js';
import type { ImageBlockComponent } from './image-block.js';
import { ImageEdgelessBlockComponent } from './image-edgeless-block.js';
import { addSiblingImageBlock } from './utils.js';
+// bytes.parse('2GB')
+const maxFileSize = 2147483648;
+
export class ImageBlockService extends BlockService {
static override readonly flavour = ImageBlockSchema.model.flavour;
static setImageProxyURL = setImageProxyMiddlewareURL;
- private readonly _fileDropOptions: FileDropOptions = {
- flavour: this.flavour,
- onDrop: async ({ files, targetModel, place, point }) => {
- const imageFiles = files.filter(file => file.type.startsWith('image/'));
- if (!imageFiles.length) return false;
-
- if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
- addSiblingImageBlock(
- this.host,
- imageFiles,
- this.maxFileSize,
- targetModel,
- place
- );
- } else if (isInsideEdgelessEditor(this.host)) {
- const gfx = this.std.get(GfxControllerIdentifier);
- point = gfx.viewport.toViewCoordFromClientCoord(point);
- await addImages(this.std, files, point);
-
- this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
- control: 'canvas:drop',
- page: 'whiteboard editor',
- module: 'toolbar',
- segment: 'toolbar',
- type: 'image',
- });
- }
-
- return true;
- },
- };
-
- fileDropManager!: FileDropManager;
-
- maxFileSize = 10 * 1000 * 1000; // 10MB (default)
-
- override mounted(): void {
- super.mounted();
-
- this.fileDropManager = new FileDropManager(this, this._fileDropOptions);
- }
+ maxFileSize = maxFileSize;
}
+export const ImageDropOption = FileDropConfigExtension({
+ flavour: ImageBlockSchema.model.flavour,
+ onDrop: ({ files, targetModel, place, point, std }) => {
+ const imageFiles = files.filter(file => file.type.startsWith('image/'));
+ if (!imageFiles.length) return false;
+
+ if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
+ addSiblingImageBlock(
+ std.host,
+ imageFiles,
+ // TODO: use max file size from service
+ maxFileSize,
+ targetModel,
+ place
+ );
+ return true;
+ }
+
+ if (isInsideEdgelessEditor(std.host)) {
+ const gfx = std.get(GfxControllerIdentifier);
+ point = gfx.viewport.toViewCoordFromClientCoord(point);
+ addImages(std, files, point).catch(console.error);
+
+ std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
+ control: 'canvas:drop',
+ page: 'whiteboard editor',
+ module: 'toolbar',
+ segment: 'toolbar',
+ type: 'image',
+ });
+ return true;
+ }
+
+ return false;
+ },
+});
+
export const ImageDragHandleOption = DragHandleConfigExtension({
flavour: ImageBlockSchema.model.flavour,
edgeless: true,
diff --git a/blocksuite/blocks/src/image-block/image-spec.ts b/blocksuite/blocks/src/image-block/image-spec.ts
index cc3409744b..b4292937de 100644
--- a/blocksuite/blocks/src/image-block/image-spec.ts
+++ b/blocksuite/blocks/src/image-block/image-spec.ts
@@ -10,7 +10,11 @@ import { literal } from 'lit/static-html.js';
import { ImageBlockAdapterExtensions } from './adapters/extension.js';
import { commands } from './commands/index.js';
-import { ImageBlockService, ImageDragHandleOption } from './image-service.js';
+import {
+ ImageBlockService,
+ ImageDragHandleOption,
+ ImageDropOption,
+} from './image-service.js';
export const ImageBlockSpec: ExtensionType[] = [
FlavourExtension('affine:image'),
@@ -29,6 +33,7 @@ export const ImageBlockSpec: ExtensionType[] = [
imageToolbar: literal`affine-image-toolbar-widget`,
}),
ImageDragHandleOption,
+ ImageDropOption,
ImageSelectionExtension,
ImageBlockAdapterExtensions,
].flat();
diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts
index b74d354beb..e792d10e72 100644
--- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts
+++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts
@@ -1,3 +1,4 @@
+import { FileDropExtension } from '@blocksuite/affine-components/drag-indicator';
import {
DNDAPIExtension,
DocDisplayMetaService,
@@ -98,6 +99,7 @@ const EdgelessCommonExtension: ExtensionType[] = [
DNDAPIExtension,
DocDisplayMetaService,
RootBlockAdapterExtensions,
+ FileDropExtension,
].flat();
export const EdgelessRootBlockSpec: ExtensionType[] = [
diff --git a/blocksuite/blocks/src/root-block/page/page-root-spec.ts b/blocksuite/blocks/src/root-block/page/page-root-spec.ts
index 21a340555d..ff03dfcbb0 100644
--- a/blocksuite/blocks/src/root-block/page/page-root-spec.ts
+++ b/blocksuite/blocks/src/root-block/page/page-root-spec.ts
@@ -1,3 +1,4 @@
+import { FileDropExtension } from '@blocksuite/affine-components/drag-indicator';
import {
DNDAPIExtension,
DocDisplayMetaService,
@@ -75,4 +76,5 @@ export const PageRootBlockSpec: ExtensionType[] = [
DNDAPIExtension,
DocDisplayMetaService,
RootBlockAdapterExtensions,
+ FileDropExtension,
].flat();
diff --git a/blocksuite/blocks/src/root-block/root-service.ts b/blocksuite/blocks/src/root-block/root-service.ts
index fe51a1f388..31f52b78b9 100644
--- a/blocksuite/blocks/src/root-block/root-service.ts
+++ b/blocksuite/blocks/src/root-block/root-service.ts
@@ -2,10 +2,6 @@ import { RootBlockSchema } from '@blocksuite/affine-model';
import type { BlockComponent } from '@blocksuite/block-std';
import { BlockService } from '@blocksuite/block-std';
-import {
- FileDropManager,
- type FileDropOptions,
-} from '../_common/components/file-drop-manager.js';
import {
HtmlTransformer,
MarkdownTransformer,
@@ -16,12 +12,6 @@ import type { RootBlockComponent } from './types.js';
export abstract class RootService extends BlockService {
static override readonly flavour = RootBlockSchema.model.flavour;
- private readonly _fileDropOptions: FileDropOptions = {
- flavour: this.flavour,
- };
-
- readonly fileDropManager = new FileDropManager(this, this._fileDropOptions);
-
transformers = {
markdown: MarkdownTransformer,
html: HtmlTransformer,
@@ -64,18 +54,6 @@ export abstract class RootService extends BlockService {
override mounted() {
super.mounted();
- this.disposables.addFromEvent(
- this.host,
- 'dragover',
- this.fileDropManager.onDragOver
- );
-
- this.disposables.addFromEvent(
- this.host,
- 'dragleave',
- this.fileDropManager.onDragLeave
- );
-
this.disposables.add(
this.std.event.add('pointerDown', ctx => {
const state = ctx.get('pointerState');
diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts
index 60b4a5622a..a15ed98715 100644
--- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts
+++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts
@@ -109,6 +109,12 @@ export class DragEventWatcher {
};
private readonly _dropHandler = (context: UIEventStateContext) => {
+ const raw = context.get('dndState').raw;
+ const fileLength = raw.dataTransfer?.files.length ?? 0;
+ // If drop files, should let file drop extension handle it
+ if (fileLength > 0) {
+ return;
+ }
this._onDrop(context);
this._std.selection.setGroup('gfx', []);
this.widget.clearRaf();
diff --git a/blocksuite/framework/block-std/src/event/control/pointer.ts b/blocksuite/framework/block-std/src/event/control/pointer.ts
index 78a98b7179..9550f87591 100644
--- a/blocksuite/framework/block-std/src/event/control/pointer.ts
+++ b/blocksuite/framework/block-std/src/event/control/pointer.ts
@@ -272,6 +272,22 @@ class DragController extends PointerControllerBase {
);
};
+ private readonly _nativeDragOver = (event: DragEvent) => {
+ const dndEventState = new DndEventState({ event });
+ this._dispatcher.run(
+ 'nativeDragOver',
+ this._createContext(event, dndEventState)
+ );
+ };
+
+ private readonly _nativeDragLeave = (event: DragEvent) => {
+ const dndEventState = new DndEventState({ event });
+ this._dispatcher.run(
+ 'nativeDragLeave',
+ this._createContext(event, dndEventState)
+ );
+ };
+
private readonly _nativeDrop = (event: DragEvent) => {
this._reset();
this._nativeDragging = false;
@@ -354,6 +370,8 @@ class DragController extends PointerControllerBase {
disposables.addFromEvent(host, 'dragend', this._nativeDragEnd);
disposables.addFromEvent(host, 'drag', this._nativeDragMove);
disposables.addFromEvent(host, 'drop', this._nativeDrop);
+ disposables.addFromEvent(host, 'dragover', this._nativeDragOver);
+ disposables.addFromEvent(host, 'dragleave', this._nativeDragLeave);
}
}
diff --git a/blocksuite/framework/block-std/src/event/dispatcher.ts b/blocksuite/framework/block-std/src/event/dispatcher.ts
index 5f77628087..1fb446389b 100644
--- a/blocksuite/framework/block-std/src/event/dispatcher.ts
+++ b/blocksuite/framework/block-std/src/event/dispatcher.ts
@@ -58,6 +58,8 @@ const eventNames = [
'nativeDragMove',
'nativeDragEnd',
'nativeDrop',
+ 'nativeDragOver',
+ 'nativeDragLeave',
...bypassEventNames,
] as const;
@@ -175,12 +177,22 @@ export class UIEventDispatcher extends LifeCycleWatcher {
this._setActive(false);
});
this.disposables.addFromEvent(this.host, 'dragover', () => {
+ _dragging = true;
+ this._setActive(true);
+ });
+ this.disposables.addFromEvent(this.host, 'dragenter', () => {
+ _dragging = true;
+ this._setActive(true);
+ });
+ this.disposables.addFromEvent(this.host, 'dragstart', () => {
+ _dragging = true;
this._setActive(true);
});
this.disposables.addFromEvent(this.host, 'dragend', () => {
- this._setActive(false);
+ _dragging = false;
});
this.disposables.addFromEvent(this.host, 'drop', () => {
+ _dragging = false;
this._setActive(true);
});
this.disposables.addFromEvent(this.host, 'pointerenter', () => {
diff --git a/blocksuite/tests-legacy/drag.spec.ts b/blocksuite/tests-legacy/drag.spec.ts
index 1c9146371e..a2cc26fe60 100644
--- a/blocksuite/tests-legacy/drag.spec.ts
+++ b/blocksuite/tests-legacy/drag.spec.ts
@@ -200,30 +200,30 @@ test('move to the last block of each level in multi-level nesting', async ({
`${testInfo.title}_drag_3_9.json`
);
+ await dragHandleFromBlockToBlockBottomById(
+ page,
+ '4',
+ '3',
+ true,
+ -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
+ );
+ await expect(page.locator('.affine-drag-indicator')).toBeHidden();
+
+ expect(await getPageSnapshot(page, true)).toMatchSnapshot(
+ `${testInfo.title}_drag_4_3.json`
+ );
+
+ await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']);
+ await dragHandleFromBlockToBlockBottomById(
+ page,
+ '3',
+ '4',
+ true,
+ -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
+ );
+ await expect(page.locator('.affine-drag-indicator')).toBeHidden();
+
// FIXME(DND)
- // await dragHandleFromBlockToBlockBottomById(
- // page,
- // '4',
- // '3',
- // true,
- // -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
- // );
- // await expect(page.locator('.affine-drag-indicator')).toBeHidden();
- //
- // expect(await getPageSnapshot(page, true)).toMatchSnapshot(
- // `${testInfo.title}_drag_4_3.json`
- // );
- //
- // await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']);
- // await dragHandleFromBlockToBlockBottomById(
- // page,
- // '3',
- // '4',
- // true,
- // -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
- // );
- // await expect(page.locator('.affine-drag-indicator')).toBeHidden();
- //
// expect(await getPageSnapshot(page, true)).toMatchSnapshot(
// `${testInfo.title}_drag_3_4.json`
// );
diff --git a/blocksuite/tests-legacy/embed-synced-doc.spec.ts b/blocksuite/tests-legacy/embed-synced-doc.spec.ts
index 9fd6b8f1a6..ab8c4e828a 100644
--- a/blocksuite/tests-legacy/embed-synced-doc.spec.ts
+++ b/blocksuite/tests-legacy/embed-synced-doc.spec.ts
@@ -259,10 +259,10 @@ test.describe('Embed synced doc', () => {
'.affine-database-column-header.database-row'
);
await databaseFirstCell.click({ force: true });
- const indicatorCount = await page
- .locator('affine-drag-indicator')
+ const selectedCount = await page
+ .locator('.affine-embed-synced-doc-container.selected')
.count();
- expect(indicatorCount).toBe(1);
+ expect(selectedCount).toBe(1);
});
});
});