diff --git a/blocksuite/affine/shared/src/services/drag-handle-config.ts b/blocksuite/affine/shared/src/services/drag-handle-config.ts index e4c7869568..7dcb2d4b63 100644 --- a/blocksuite/affine/shared/src/services/drag-handle-config.ts +++ b/blocksuite/affine/shared/src/services/drag-handle-config.ts @@ -6,8 +6,6 @@ import { import { type Container, createIdentifier } from '@blocksuite/global/di'; import { Job, Slice, type SliceSnapshot } from '@blocksuite/store'; -export type DropType = 'before' | 'after' | 'in'; - export const DndApiExtensionIdentifier = createIdentifier( 'AffineDndApiIdentifier' ); diff --git a/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts b/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts index 2ac5d18ecf..cdc36511ca 100644 --- a/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts +++ b/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts @@ -2,6 +2,7 @@ import type { BlockComponent } from '@blocksuite/block-std'; import { type Point, Rect } from '@blocksuite/global/utils'; import type { BlockModel } from '@blocksuite/store'; +import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from '../../consts/index.js'; import { getClosestBlockComponentByElement, getRectByBlockComponent, @@ -10,6 +11,24 @@ import { matchFlavours } from '../model/index.js'; import { getDropRectByPoint } from './get-drop-rect-by-point.js'; import { DropFlags, type DroppingType, type DropResult } from './types.js'; +function getVisiblePreviousElementSibling(element: Element | null) { + if (!element) return null; + let prev = element.previousElementSibling; + while (prev && !prev.checkVisibility()) { + prev = prev.previousElementSibling; + } + return prev; +} + +function getVisibleNextElementSibling(element: Element | null) { + if (!element) return null; + let next = element.nextElementSibling; + while (next && !next.checkVisibility()) { + next = next.nextElementSibling; + } + return next; +} + /** * Calculates the drop target. */ @@ -19,7 +38,10 @@ export function calcDropTarget( element: Element, draggingElements: BlockComponent[] = [], scale: number = 1, - flavour: string | null = null // for block-hub + /** + * Allow the dragging block to be dropped as sublist + */ + allowSublist: boolean = true ): DropResult | null { const schema = model.doc.getSchemaByFlavour( 'affine:database' as BlockSuite.Flavour @@ -28,14 +50,10 @@ export function calcDropTarget( let shouldAppendToDatabase = true; - if (children.length) { - if (draggingElements.length) { - shouldAppendToDatabase = draggingElements - .map(el => el.model) - .every(m => children.includes(m.flavour)); - } else if (flavour) { - shouldAppendToDatabase = children.includes(flavour); - } + if (children.length && draggingElements.length) { + shouldAppendToDatabase = draggingElements + .map(el => el.model) + .every(m => children.includes(m.flavour)); } if ( @@ -107,7 +125,7 @@ export function calcDropTarget( let prev; let prevRect; - prev = element.previousElementSibling; + prev = getVisiblePreviousElementSibling(element); if (prev) { if ( draggingElements.length && @@ -118,7 +136,7 @@ export function calcDropTarget( prevRect = getRectByBlockComponent(prev); } } else { - prev = element.parentElement?.previousElementSibling; + prev = getVisiblePreviousElementSibling(element.parentElement); if (prev) { prevRect = prev.getBoundingClientRect(); } @@ -128,20 +146,37 @@ export function calcDropTarget( offsetY = (domRect.top - prevRect.bottom) / 2; } } else { + // Only consider drop as children when target block is list block. + // To drop in, the position must after the target first + // If drop in target has children, we can use insert before or after of that children + // to achieve the same effect. + const hasChild = (element as BlockComponent).childBlocks.length; + if ( + allowSublist && + matchFlavours(model, ['affine:list']) && + !hasChild && + point.x > domRect.x + BLOCK_CHILDREN_CONTAINER_PADDING_LEFT + ) { + type = 'in'; + } // after let next; let nextRect; - next = element.nextElementSibling; + next = getVisibleNextElementSibling(element); if (next) { - if (draggingElements.length && next === draggingElements[0]) { + if ( + type === 'after' && + draggingElements.length && + next === draggingElements[0] + ) { type = 'none'; next = null; } } else { - next = getClosestBlockComponentByElement( - element.parentElement - )?.nextElementSibling; + next = getVisibleNextElementSibling( + getClosestBlockComponentByElement(element.parentElement) + ); } if (next) { @@ -159,6 +194,11 @@ export function calcDropTarget( top += domRect.height + offsetY; } + if (type === 'in') { + domRect.x += BLOCK_CHILDREN_CONTAINER_PADDING_LEFT; + domRect.width -= BLOCK_CHILDREN_CONTAINER_PADDING_LEFT; + } + return { type, rect: Rect.fromLWTH(domRect.left, domRect.width, top - height / 2, height), diff --git a/blocksuite/affine/shared/src/utils/dnd/index.ts b/blocksuite/affine/shared/src/utils/dnd/index.ts index 8dc9c445ab..9087842346 100644 --- a/blocksuite/affine/shared/src/utils/dnd/index.ts +++ b/blocksuite/affine/shared/src/utils/dnd/index.ts @@ -1,3 +1,3 @@ export * from './calc-drop-target.js'; export * from './get-drop-rect-by-point.js'; -export type { DropResult } from './types.js'; +export * from './types.js'; diff --git a/blocksuite/affine/shared/src/utils/dnd/types.ts b/blocksuite/affine/shared/src/utils/dnd/types.ts index 3aad16d973..8fa69a3c68 100644 --- a/blocksuite/affine/shared/src/utils/dnd/types.ts +++ b/blocksuite/affine/shared/src/utils/dnd/types.ts @@ -20,7 +20,7 @@ export enum DropFlags { /** * A dropping type. */ -export type DroppingType = 'none' | 'before' | 'after' | 'database'; +export type DroppingType = 'none' | 'before' | 'after' | 'database' | 'in'; export type DropResult = { type: DroppingType; diff --git a/blocksuite/blocks/src/database-block/database-block.ts b/blocksuite/blocks/src/database-block/database-block.ts index 3d14beefb2..9cfeffecd0 100644 --- a/blocksuite/blocks/src/database-block/database-block.ts +++ b/blocksuite/blocks/src/database-block/database-block.ts @@ -259,9 +259,9 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent< return () => { this.indicator.remove(); const model = this.doc.getBlock(id)?.model; - const target = this.doc.getBlock(result.dropBlockId)?.model ?? null; - let parent = this.doc.getParent(result.dropBlockId); - const shouldInsertIn = result.dropType === 'in'; + const target = result.modelState.model; + let parent = this.doc.getParent(target.id); + const shouldInsertIn = result.type === 'in'; if (shouldInsertIn) { parent = target; } @@ -273,7 +273,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent< [model], parent, target, - result.dropType === 'before' + result.type === 'before' ); } } diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts index 919417333f..a408dc811c 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts @@ -1,6 +1,3 @@ -import type { DropType } from '@blocksuite/affine-shared/services'; -import type { Rect } from '@blocksuite/global/utils'; - export const DRAG_HANDLE_CONTAINER_HEIGHT = 24; export const DRAG_HANDLE_CONTAINER_WIDTH = 16; export const DRAG_HANDLE_CONTAINER_WIDTH_TOP_LEVEL = 8; @@ -20,9 +17,3 @@ export const HOVER_AREA_RECT_PADDING_TOP_LEVEL = 6; export const NOTE_CONTAINER_PADDING = 24; export const EDGELESS_NOTE_EXTRA_PADDING = 20; export const DRAG_HOVER_RECT_PADDING = 4; - -export type DropResult = { - rect: Rect | null; - dropBlockId: string; - dropType: DropType; -}; diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts index 2a49c3f877..40ae22bb5f 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts @@ -1,9 +1,9 @@ import type { RootBlockModel } from '@blocksuite/affine-model'; +import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { - DocModeProvider, - type DropType, -} from '@blocksuite/affine-shared/services'; -import { + calcDropTarget, + type DroppingType, + type DropResult, getScrollContainer, isInsideEdgelessEditor, isInsidePageEditor, @@ -27,14 +27,12 @@ import { autoScroll } from '../../../root-block/text-selection/utils.js'; import type { EdgelessRootService } from '../../edgeless/index.js'; import type { DragPreview } from './components/drag-preview.js'; import type { DropIndicator } from './components/drop-indicator.js'; -import type { DropResult } from './config.js'; import type { AFFINE_DRAG_HANDLE_WIDGET } from './consts.js'; import { PreviewHelper } from './helpers/preview-helper.js'; import { RectHelper } from './helpers/rect-helper.js'; import { SelectionHelper } from './helpers/selection-helper.js'; import { styles } from './styles.js'; import { - calcDropTarget, containBlock, containChildBlock, getClosestBlockByPoint, @@ -107,9 +105,6 @@ export class AffineDragHandleWidget extends WidgetComponent { return null; } - let rect = null; - let dropType: DropType = 'before'; - const result = calcDropTarget( point, model, @@ -119,20 +114,9 @@ export class AffineDragHandleWidget extends WidgetComponent { isDraggedElementNote === false ); - if (result) { - rect = result.rect; - dropType = result.dropType; - } + if (isDraggedElementNote && result?.type === 'in') return null; - if (isDraggedElementNote && dropType === 'in') return null; - - const dropIndicator = { - rect, - dropBlockId: blockId, - dropType, - }; - - return dropIndicator; + return result; }; private readonly _handleEventWatcher = new HandleEventWatcher(this); @@ -181,8 +165,8 @@ export class AffineDragHandleWidget extends WidgetComponent { private readonly _updateDropResult = (dropResult: DropResult | null) => { if (!this.dropIndicator) return; - this.dropBlockId = dropResult?.dropBlockId ?? ''; - this.dropType = dropResult?.dropType ?? null; + this.dropBlockId = dropResult?.modelState.model.id ?? ''; + this.dropType = dropResult?.type ?? null; if (dropResult?.rect) { const offsetParentRect = this.dragHandleContainerOffsetParent.getBoundingClientRect(); @@ -237,7 +221,7 @@ export class AffineDragHandleWidget extends WidgetComponent { dropIndicator: DropIndicator | null = null; - dropType: DropType | null = null; + dropType: DroppingType | null = null; edgelessWatcher = new EdgelessWatcher(this); diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/utils.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/utils.ts index 4d76056cd9..16988a00ac 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/utils.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/utils.ts @@ -1,17 +1,12 @@ import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph'; import type { ParagraphBlockModel } from '@blocksuite/affine-model'; -import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from '@blocksuite/affine-shared/consts'; -import { - DocModeProvider, - type DropType, -} from '@blocksuite/affine-shared/services'; +import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { + calcDropTarget, + type DropResult, findClosestBlockComponent, getBlockProps, - getClosestBlockComponentByElement, getClosestBlockComponentByPoint, - getDropRectByPoint, - getRectByBlockComponent, matchFlavours, } from '@blocksuite/affine-shared/utils'; import type { @@ -26,7 +21,6 @@ import { DRAG_HANDLE_CONTAINER_HEIGHT, DRAG_HANDLE_CONTAINER_OFFSET_LEFT, DRAG_HANDLE_CONTAINER_OFFSET_LEFT_LIST, - type DropResult, EDGELESS_NOTE_EXTRA_PADDING, NOTE_CONTAINER_PADDING, } from './config.js'; @@ -181,116 +175,6 @@ export const getClosestBlockByPoint = ( return closestBlock; }; -export function calcDropTarget( - point: Point, - model: BlockModel, - element: Element, - draggingElements: BlockComponent[], - scale: number, - /** - * Allow the dragging block to be dropped as sublist - */ - allowSublist: boolean = true -): DropResult | null { - let type: DropType | 'none' = 'none'; - const height = 3 * scale; - const dropRect = getDropRectByPoint(point, model, element); - if (!dropRect) return null; - const { rect: domRect } = dropRect; - - const distanceToTop = Math.abs(domRect.top - point.y); - const distanceToBottom = Math.abs(domRect.bottom - point.y); - const before = distanceToTop < distanceToBottom; - - type = before ? 'before' : 'after'; - let offsetY = 4; - - if (type === 'before') { - // before - let prev; - let prevRect; - - prev = element.previousElementSibling; - if (prev) { - if ( - draggingElements.length && - prev === draggingElements[draggingElements.length - 1] - ) { - type = 'none'; - } else { - prevRect = getRectByBlockComponent(prev); - } - } else { - prev = element.parentElement?.previousElementSibling; - if (prev) { - prevRect = prev.getBoundingClientRect(); - } - } - - if (prevRect) { - offsetY = (domRect.top - prevRect.bottom) / 2; - } - } else { - // Only consider drop as children when target block is list block. - // To drop in, the position must after the target first - // If drop in target has children, we can use insert before or after of that children - // to achieve the same effect. - const hasChild = (element as BlockComponent).childBlocks.length; - if ( - allowSublist && - matchFlavours(model, ['affine:list']) && - !hasChild && - point.x > domRect.x + BLOCK_CHILDREN_CONTAINER_PADDING_LEFT - ) { - type = 'in'; - } - // after - let next; - let nextRect; - - next = element.nextElementSibling; - if (next) { - if ( - type === 'after' && - draggingElements.length && - next === draggingElements[0] - ) { - type = 'none'; - next = null; - } - } else { - next = getClosestBlockComponentByElement( - element.parentElement - )?.nextElementSibling; - } - - if (next) { - nextRect = getRectByBlockComponent(next); - offsetY = (nextRect.top - domRect.bottom) / 2; - } - } - - if (type === 'none') return null; - - let top = domRect.top; - if (type === 'before') { - top -= offsetY; - } else { - top += domRect.height + offsetY; - } - - if (type === 'in') { - domRect.x += BLOCK_CHILDREN_CONTAINER_PADDING_LEFT; - domRect.width -= BLOCK_CHILDREN_CONTAINER_PADDING_LEFT; - } - - return { - rect: Rect.fromLWTH(domRect.left, domRect.width, top - height / 2, height), - dropBlockId: model.id, - dropType: type, - }; -} - export const getDropResult = ( event: MouseEvent, scale: number = 1