mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
refactor: remove legacy drag handle logic (#9246)
This commit is contained in:
+19
-22
@@ -1,25 +1,14 @@
|
|||||||
import {
|
|
||||||
getClosestBlockComponentByElement,
|
|
||||||
getRectByBlockComponent,
|
|
||||||
matchFlavours,
|
|
||||||
} from '@blocksuite/affine-shared/utils';
|
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
import { type Point, Rect } from '@blocksuite/global/utils';
|
import { type Point, Rect } from '@blocksuite/global/utils';
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
import type { EditingState } from '../types.js';
|
import {
|
||||||
import { DropFlags, getDropRectByPoint } from './query.js';
|
getClosestBlockComponentByElement,
|
||||||
|
getRectByBlockComponent,
|
||||||
/**
|
} from '../dom/index.js';
|
||||||
* A dropping type.
|
import { matchFlavours } from '../model/index.js';
|
||||||
*/
|
import { getDropRectByPoint } from './get-drop-rect-by-point.js';
|
||||||
export type DroppingType = 'none' | 'before' | 'after' | 'database';
|
import { DropFlags, type DroppingType, type DropResult } from './types.js';
|
||||||
|
|
||||||
export type DropResult = {
|
|
||||||
type: DroppingType;
|
|
||||||
rect: Rect;
|
|
||||||
modelState: EditingState;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the drop target.
|
* Calculates the drop target.
|
||||||
@@ -32,7 +21,9 @@ export function calcDropTarget(
|
|||||||
scale: number = 1,
|
scale: number = 1,
|
||||||
flavour: string | null = null // for block-hub
|
flavour: string | null = null // for block-hub
|
||||||
): DropResult | null {
|
): DropResult | null {
|
||||||
const schema = model.doc.getSchemaByFlavour('affine:database');
|
const schema = model.doc.getSchemaByFlavour(
|
||||||
|
'affine:database' as BlockSuite.Flavour
|
||||||
|
);
|
||||||
const children = schema?.model.children ?? [];
|
const children = schema?.model.children ?? [];
|
||||||
|
|
||||||
let shouldAppendToDatabase = true;
|
let shouldAppendToDatabase = true;
|
||||||
@@ -47,8 +38,12 @@ export function calcDropTarget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldAppendToDatabase && !matchFlavours(model, ['affine:database'])) {
|
if (
|
||||||
const databaseBlockComponent = element.closest('affine-database');
|
!shouldAppendToDatabase &&
|
||||||
|
!matchFlavours(model, ['affine:database' as BlockSuite.Flavour])
|
||||||
|
) {
|
||||||
|
const databaseBlockComponent =
|
||||||
|
element.closest<BlockComponent>('affine-database');
|
||||||
if (databaseBlockComponent) {
|
if (databaseBlockComponent) {
|
||||||
element = databaseBlockComponent;
|
element = databaseBlockComponent;
|
||||||
model = databaseBlockComponent.model;
|
model = databaseBlockComponent.model;
|
||||||
@@ -57,7 +52,9 @@ export function calcDropTarget(
|
|||||||
|
|
||||||
let type: DroppingType = 'none';
|
let type: DroppingType = 'none';
|
||||||
const height = 3 * scale;
|
const height = 3 * scale;
|
||||||
const { rect: domRect, flag } = getDropRectByPoint(point, model, element);
|
const dropResult = getDropRectByPoint(point, model, element);
|
||||||
|
if (!dropResult) return null;
|
||||||
|
const { rect: domRect, flag } = dropResult;
|
||||||
|
|
||||||
if (flag === DropFlags.EmptyDatabase) {
|
if (flag === DropFlags.EmptyDatabase) {
|
||||||
// empty database
|
// empty database
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import { BLOCK_ID_ATTR } from '@blocksuite/block-std';
|
||||||
|
import type { Point } from '@blocksuite/global/utils';
|
||||||
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { getRectByBlockComponent } from '../dom/index.js';
|
||||||
|
import { matchFlavours } from '../model/index.js';
|
||||||
|
import { DropFlags } from './types.js';
|
||||||
|
|
||||||
|
const ATTR_SELECTOR = `[${BLOCK_ID_ATTR}]`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the drop rect by block and point.
|
||||||
|
*/
|
||||||
|
export function getDropRectByPoint(
|
||||||
|
point: Point,
|
||||||
|
model: BlockModel,
|
||||||
|
element: Element
|
||||||
|
): null | {
|
||||||
|
rect: DOMRect;
|
||||||
|
flag: DropFlags;
|
||||||
|
} {
|
||||||
|
const result = {
|
||||||
|
rect: getRectByBlockComponent(element),
|
||||||
|
flag: DropFlags.Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDatabase = matchFlavours(model, [
|
||||||
|
'affine:database' as BlockSuite.Flavour,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isDatabase) {
|
||||||
|
const table = getDatabaseBlockTableElement(element);
|
||||||
|
if (!table) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = table.getBoundingClientRect();
|
||||||
|
if (model.isEmpty.value) {
|
||||||
|
result.flag = DropFlags.EmptyDatabase;
|
||||||
|
|
||||||
|
if (point.y < bounds.top) return result;
|
||||||
|
|
||||||
|
const header = getDatabaseBlockColumnHeaderElement(element);
|
||||||
|
if (!header) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = header.getBoundingClientRect();
|
||||||
|
result.rect = new DOMRect(
|
||||||
|
result.rect.left,
|
||||||
|
bounds.bottom,
|
||||||
|
result.rect.width,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.flag = DropFlags.Database;
|
||||||
|
const rows = getDatabaseBlockRowsElement(element);
|
||||||
|
if (!rows) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const rowsBounds = rows.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (point.y < rowsBounds.top || point.y > rowsBounds.bottom) return result;
|
||||||
|
|
||||||
|
const elements = document.elementsFromPoint(point.x, point.y);
|
||||||
|
const len = elements.length;
|
||||||
|
let e;
|
||||||
|
let i = 0;
|
||||||
|
for (; i < len; i++) {
|
||||||
|
e = elements[i];
|
||||||
|
|
||||||
|
if (e.classList.contains('affine-database-block-row-cell-content')) {
|
||||||
|
const cellRect = getCellRect(e, bounds);
|
||||||
|
if (!cellRect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result.rect = cellRect;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.classList.contains('affine-database-block-row')) {
|
||||||
|
e = e.querySelector(ATTR_SELECTOR);
|
||||||
|
if (!e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cellRect = getCellRect(e, bounds);
|
||||||
|
if (!cellRect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result.rect = cellRect;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = element.parentElement;
|
||||||
|
if (parent?.classList.contains('affine-database-block-row-cell-content')) {
|
||||||
|
result.flag = DropFlags.Database;
|
||||||
|
const cellRect = getCellRect(parent);
|
||||||
|
if (!cellRect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result.rect = cellRect;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the table of the database.
|
||||||
|
*/
|
||||||
|
function getDatabaseBlockTableElement(element: Element) {
|
||||||
|
return element.querySelector('.affine-database-block-table');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the column header of the database.
|
||||||
|
*/
|
||||||
|
function getDatabaseBlockColumnHeaderElement(element: Element) {
|
||||||
|
return element.querySelector('.affine-database-column-header');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the rows of the database.
|
||||||
|
*/
|
||||||
|
function getDatabaseBlockRowsElement(element: Element) {
|
||||||
|
return element.querySelector('.affine-database-block-rows');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellRect(element: Element, bounds?: DOMRect) {
|
||||||
|
if (!bounds) {
|
||||||
|
const table = element.closest('.affine-database-block-table');
|
||||||
|
if (!table) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
bounds = table.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
// affine-database-block-row-cell
|
||||||
|
const col = element.parentElement;
|
||||||
|
if (!col) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// affine-database-block-row
|
||||||
|
const row = col.parentElement;
|
||||||
|
if (!row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const colRect = col.getBoundingClientRect();
|
||||||
|
return new DOMRect(
|
||||||
|
bounds.left,
|
||||||
|
colRect.top,
|
||||||
|
colRect.right - bounds.left,
|
||||||
|
colRect.height
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
export * from './calc-drop-target.js';
|
||||||
|
export * from './get-drop-rect-by-point.js';
|
||||||
export * from './legacy.js';
|
export * from './legacy.js';
|
||||||
|
export type { DropResult } from './types.js';
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
|
import type { Rect } from '@blocksuite/global/utils';
|
||||||
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
|
export interface EditingState {
|
||||||
|
element: BlockComponent;
|
||||||
|
model: BlockModel;
|
||||||
|
rect: DOMRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a flag for the drop target.
|
||||||
|
*/
|
||||||
|
export enum DropFlags {
|
||||||
|
Normal,
|
||||||
|
Database,
|
||||||
|
EmptyDatabase,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dropping type.
|
||||||
|
*/
|
||||||
|
export type DroppingType = 'none' | 'before' | 'after' | 'database';
|
||||||
|
|
||||||
|
export type DropResult = {
|
||||||
|
type: DroppingType;
|
||||||
|
rect: Rect;
|
||||||
|
modelState: EditingState;
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { DragIndicator } from '@blocksuite/affine-components/drag-indicator';
|
import type { DragIndicator } from '@blocksuite/affine-components/drag-indicator';
|
||||||
import {
|
import {
|
||||||
|
calcDropTarget,
|
||||||
|
type DropResult,
|
||||||
getClosestBlockComponentByPoint,
|
getClosestBlockComponentByPoint,
|
||||||
isInsidePageEditor,
|
isInsidePageEditor,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
@@ -9,8 +11,6 @@ import type { IVec } from '@blocksuite/global/utils';
|
|||||||
import { assertExists, Point } from '@blocksuite/global/utils';
|
import { assertExists, Point } from '@blocksuite/global/utils';
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
import { calcDropTarget, type DropResult } from '../../_common/utils/index.js';
|
|
||||||
|
|
||||||
export type onDropProps = {
|
export type onDropProps = {
|
||||||
files: File[];
|
files: File[];
|
||||||
targetModel: BlockModel | null;
|
targetModel: BlockModel | null;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Compat with SSR
|
// Compat with SSR
|
||||||
export * from '../types.js';
|
export * from '../types.js';
|
||||||
export * from './drag-and-drop.js';
|
|
||||||
export * from './query.js';
|
export * from './query.js';
|
||||||
export {
|
export {
|
||||||
createButtonPopper,
|
createButtonPopper,
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
import {
|
import type { EditorHost } from '@blocksuite/block-std';
|
||||||
getRectByBlockComponent,
|
|
||||||
matchFlavours,
|
|
||||||
} from '@blocksuite/affine-shared/utils';
|
|
||||||
import { BLOCK_ID_ATTR, type EditorHost } from '@blocksuite/block-std';
|
|
||||||
import type { Point } from '@blocksuite/global/utils';
|
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
import type { RootBlockComponent } from '../../index.js';
|
import type { RootBlockComponent } from '../../index.js';
|
||||||
|
|
||||||
const ATTR_SELECTOR = `[${BLOCK_ID_ATTR}]`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to build model's "normal" block path.
|
* This function is used to build model's "normal" block path.
|
||||||
* If this function does not meet your needs, you may need to build path manually to satisfy your needs.
|
* If this function does not meet your needs, you may need to build path manually to satisfy your needs.
|
||||||
@@ -59,150 +51,6 @@ export function getBlockComponentByModel(
|
|||||||
return editorHost.view.getBlock(model.id);
|
return editorHost.view.getBlock(model.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEdgelessChildNote({ classList }: Element) {
|
|
||||||
return classList.contains('note-background');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get hovering note with given a point in edgeless mode.
|
|
||||||
*/
|
|
||||||
export function getHoveringNote(point: Point) {
|
|
||||||
return (
|
|
||||||
document.elementsFromPoint(point.x, point.y).find(isEdgelessChildNote) ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the table of the database.
|
|
||||||
*/
|
|
||||||
function getDatabaseBlockTableElement(element: Element) {
|
|
||||||
return element.querySelector('.affine-database-block-table');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the column header of the database.
|
|
||||||
*/
|
|
||||||
function getDatabaseBlockColumnHeaderElement(element: Element) {
|
|
||||||
return element.querySelector('.affine-database-column-header');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the rows of the database.
|
|
||||||
*/
|
|
||||||
function getDatabaseBlockRowsElement(element: Element) {
|
|
||||||
return element.querySelector('.affine-database-block-rows');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a flag for the drop target.
|
|
||||||
*/
|
|
||||||
export enum DropFlags {
|
|
||||||
Normal,
|
|
||||||
Database,
|
|
||||||
EmptyDatabase,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the drop rect by block and point.
|
|
||||||
*/
|
|
||||||
export function getDropRectByPoint(
|
|
||||||
point: Point,
|
|
||||||
model: BlockModel,
|
|
||||||
element: Element
|
|
||||||
): {
|
|
||||||
rect: DOMRect;
|
|
||||||
flag: DropFlags;
|
|
||||||
} {
|
|
||||||
const result = {
|
|
||||||
rect: getRectByBlockComponent(element),
|
|
||||||
flag: DropFlags.Normal,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDatabase = matchFlavours(model, ['affine:database']);
|
|
||||||
|
|
||||||
if (isDatabase) {
|
|
||||||
const table = getDatabaseBlockTableElement(element);
|
|
||||||
if (!table) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
let bounds = table.getBoundingClientRect();
|
|
||||||
if (model.isEmpty.value) {
|
|
||||||
result.flag = DropFlags.EmptyDatabase;
|
|
||||||
|
|
||||||
if (point.y < bounds.top) return result;
|
|
||||||
|
|
||||||
const header = getDatabaseBlockColumnHeaderElement(element);
|
|
||||||
assertExists(header);
|
|
||||||
bounds = header.getBoundingClientRect();
|
|
||||||
result.rect = new DOMRect(
|
|
||||||
result.rect.left,
|
|
||||||
bounds.bottom,
|
|
||||||
result.rect.width,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result.flag = DropFlags.Database;
|
|
||||||
const rows = getDatabaseBlockRowsElement(element);
|
|
||||||
assertExists(rows);
|
|
||||||
const rowsBounds = rows.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (point.y < rowsBounds.top || point.y > rowsBounds.bottom)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
const elements = document.elementsFromPoint(point.x, point.y);
|
|
||||||
const len = elements.length;
|
|
||||||
let e;
|
|
||||||
let i = 0;
|
|
||||||
for (; i < len; i++) {
|
|
||||||
e = elements[i];
|
|
||||||
|
|
||||||
if (e.classList.contains('affine-database-block-row-cell-content')) {
|
|
||||||
result.rect = getCellRect(e, bounds);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.classList.contains('affine-database-block-row')) {
|
|
||||||
e = e.querySelector(ATTR_SELECTOR);
|
|
||||||
assertExists(e);
|
|
||||||
result.rect = getCellRect(e, bounds);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const parent = element.parentElement;
|
|
||||||
if (parent?.classList.contains('affine-database-block-row-cell-content')) {
|
|
||||||
result.flag = DropFlags.Database;
|
|
||||||
result.rect = getCellRect(parent);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCellRect(element: Element, bounds?: DOMRect) {
|
|
||||||
if (!bounds) {
|
|
||||||
const table = element.closest('.affine-database-block-table');
|
|
||||||
assertExists(table);
|
|
||||||
bounds = table.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
// affine-database-block-row-cell
|
|
||||||
const col = element.parentElement;
|
|
||||||
assertExists(col);
|
|
||||||
// affine-database-block-row
|
|
||||||
const row = col.parentElement;
|
|
||||||
assertExists(row);
|
|
||||||
const colRect = col.getBoundingClientRect();
|
|
||||||
return new DOMRect(
|
|
||||||
bounds.left,
|
|
||||||
colRect.top,
|
|
||||||
colRect.right - bounds.left,
|
|
||||||
colRect.height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return `true` if the element has class name in the class list.
|
* Return `true` if the element has class name in the class list.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||||
import {
|
import {
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
DragHandleConfigIdentifier,
|
|
||||||
type DropType,
|
type DropType,
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
@@ -49,7 +47,6 @@ import { DragEventWatcher } from './watchers/drag-event-watcher.js';
|
|||||||
import { EdgelessWatcher } from './watchers/edgeless-watcher.js';
|
import { EdgelessWatcher } from './watchers/edgeless-watcher.js';
|
||||||
import { HandleEventWatcher } from './watchers/handle-event-watcher.js';
|
import { HandleEventWatcher } from './watchers/handle-event-watcher.js';
|
||||||
import { KeyboardEventWatcher } from './watchers/keyboard-event-watcher.js';
|
import { KeyboardEventWatcher } from './watchers/keyboard-event-watcher.js';
|
||||||
import { LegacyDragEventWatcher } from './watchers/legacy-drag-event-watcher.js';
|
|
||||||
import { PageWatcher } from './watchers/page-watcher.js';
|
import { PageWatcher } from './watchers/page-watcher.js';
|
||||||
import { PointerEventWatcher } from './watchers/pointer-event-watcher.js';
|
import { PointerEventWatcher } from './watchers/pointer-event-watcher.js';
|
||||||
|
|
||||||
@@ -143,8 +140,6 @@ export class AffineDragHandleWidget extends WidgetComponent<RootBlockModel> {
|
|||||||
|
|
||||||
private readonly _keyboardEventWatcher = new KeyboardEventWatcher(this);
|
private readonly _keyboardEventWatcher = new KeyboardEventWatcher(this);
|
||||||
|
|
||||||
private readonly _legacyDragEventWatcher = new LegacyDragEventWatcher(this);
|
|
||||||
|
|
||||||
private readonly _pageWatcher = new PageWatcher(this);
|
private readonly _pageWatcher = new PageWatcher(this);
|
||||||
|
|
||||||
private readonly _removeDropIndicator = () => {
|
private readonly _removeDropIndicator = () => {
|
||||||
@@ -360,10 +355,6 @@ export class AffineDragHandleWidget extends WidgetComponent<RootBlockModel> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private get _enableNewDnd() {
|
|
||||||
return this.std.doc.awarenessStore.getFlag('enable_new_dnd') ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get dragHandleContainerOffsetParent() {
|
get dragHandleContainerOffsetParent() {
|
||||||
return this.dragHandleContainer.parentElement!;
|
return this.dragHandleContainer.parentElement!;
|
||||||
}
|
}
|
||||||
@@ -385,17 +376,10 @@ export class AffineDragHandleWidget extends WidgetComponent<RootBlockModel> {
|
|||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.std.provider.getAll(DragHandleConfigIdentifier).forEach(config => {
|
|
||||||
this.optionRunner.register(config);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pointerEventWatcher.watch();
|
this.pointerEventWatcher.watch();
|
||||||
this._keyboardEventWatcher.watch();
|
this._keyboardEventWatcher.watch();
|
||||||
if (this._enableNewDnd) {
|
this._dragEventWatcher.watch();
|
||||||
this._dragEventWatcher.watch();
|
|
||||||
} else {
|
|
||||||
this._legacyDragEventWatcher.watch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override disconnectedCallback() {
|
override disconnectedCallback() {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import { getCurrentNativeRange } from '@blocksuite/affine-shared/utils';
|
import { getCurrentNativeRange } from '@blocksuite/affine-shared/utils';
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
import { Rect } from '@blocksuite/global/utils';
|
import { Rect } from '@blocksuite/global/utils';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import { findNoteBlockModel } from '@blocksuite/affine-shared/utils';
|
import { findNoteBlockModel } from '@blocksuite/affine-shared/utils';
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
|
|
||||||
@@ -47,14 +46,9 @@ export class SelectionHelper {
|
|||||||
.filter((block): block is BlockComponent => !!block);
|
.filter((block): block is BlockComponent => !!block);
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedBlockIds() {
|
|
||||||
return this.selectedBlocks.map(block => block.blockId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedBlocks() {
|
get selectedBlocks() {
|
||||||
const selection = this.selection;
|
const selection = this.selection;
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
return selection.find('text')
|
return selection.find('text')
|
||||||
? selection.filter('text')
|
? selection.filter('text')
|
||||||
: selection.filter('block');
|
: selection.filter('block');
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
getBlockProps,
|
getBlockProps,
|
||||||
getClosestBlockComponentByElement,
|
getClosestBlockComponentByElement,
|
||||||
getClosestBlockComponentByPoint,
|
getClosestBlockComponentByPoint,
|
||||||
|
getDropRectByPoint,
|
||||||
getRectByBlockComponent,
|
getRectByBlockComponent,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
@@ -21,10 +22,6 @@ import type {
|
|||||||
import { Point, Rect } from '@blocksuite/global/utils';
|
import { Point, Rect } from '@blocksuite/global/utils';
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
|
||||||
import {
|
|
||||||
getDropRectByPoint,
|
|
||||||
getHoveringNote,
|
|
||||||
} from '../../../_common/utils/index.js';
|
|
||||||
import {
|
import {
|
||||||
DRAG_HANDLE_CONTAINER_HEIGHT,
|
DRAG_HANDLE_CONTAINER_HEIGHT,
|
||||||
DRAG_HANDLE_CONTAINER_OFFSET_LEFT,
|
DRAG_HANDLE_CONTAINER_OFFSET_LEFT,
|
||||||
@@ -197,7 +194,9 @@ export function calcDropTarget(
|
|||||||
): DropResult | null {
|
): DropResult | null {
|
||||||
let type: DropType | 'none' = 'none';
|
let type: DropType | 'none' = 'none';
|
||||||
const height = 3 * scale;
|
const height = 3 * scale;
|
||||||
const { rect: domRect } = getDropRectByPoint(point, model, element);
|
const dropRect = getDropRectByPoint(point, model, element);
|
||||||
|
if (!dropRect) return null;
|
||||||
|
const { rect: domRect } = dropRect;
|
||||||
|
|
||||||
const distanceToTop = Math.abs(domRect.top - point.y);
|
const distanceToTop = Math.abs(domRect.top - point.y);
|
||||||
const distanceToBottom = Math.abs(domRect.bottom - point.y);
|
const distanceToBottom = Math.abs(domRect.bottom - point.y);
|
||||||
@@ -348,3 +347,17 @@ export function getDuplicateBlocks(blocks: BlockModel[]) {
|
|||||||
}));
|
}));
|
||||||
return duplicateBlocks;
|
return duplicateBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hovering note with given a point in edgeless mode.
|
||||||
|
*/
|
||||||
|
function getHoveringNote(point: Point) {
|
||||||
|
return (
|
||||||
|
document.elementsFromPoint(point.x, point.y).find(isEdgelessChildNote) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEdgelessChildNote({ classList }: Element) {
|
||||||
|
return classList.contains('note-background');
|
||||||
|
}
|
||||||
|
|||||||
+3
-30
@@ -9,7 +9,9 @@ import {
|
|||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
|
calcDropTarget,
|
||||||
captureEventTarget,
|
captureEventTarget,
|
||||||
|
type DropResult,
|
||||||
getBlockComponentsExcludeSubtrees,
|
getBlockComponentsExcludeSubtrees,
|
||||||
getClosestBlockComponentByPoint,
|
getClosestBlockComponentByPoint,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
@@ -25,14 +27,6 @@ import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
|||||||
import { Bound, Point } from '@blocksuite/global/utils';
|
import { Bound, Point } from '@blocksuite/global/utils';
|
||||||
import { Job, Slice, type SliceSnapshot } from '@blocksuite/store';
|
import { Job, Slice, type SliceSnapshot } from '@blocksuite/store';
|
||||||
|
|
||||||
import {
|
|
||||||
HtmlAdapter,
|
|
||||||
MarkdownAdapter,
|
|
||||||
} from '../../../../_common/adapters/index.js';
|
|
||||||
import {
|
|
||||||
calcDropTarget,
|
|
||||||
type DropResult,
|
|
||||||
} from '../../../../_common/utils/index.js';
|
|
||||||
import type { EdgelessRootBlockComponent } from '../../../edgeless/index.js';
|
import type { EdgelessRootBlockComponent } from '../../../edgeless/index.js';
|
||||||
import { addNoteAtPoint } from '../../../edgeless/utils/common.js';
|
import { addNoteAtPoint } from '../../../edgeless/utils/common.js';
|
||||||
import { DropIndicator } from '../components/drop-indicator.js';
|
import { DropIndicator } from '../components/drop-indicator.js';
|
||||||
@@ -493,28 +487,7 @@ export class DragEventWatcher {
|
|||||||
return slice;
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = dataTransfer.getData('text/html');
|
return null;
|
||||||
if (html) {
|
|
||||||
// use html parser;
|
|
||||||
const htmlAdapter = new HtmlAdapter(job);
|
|
||||||
const slice = await htmlAdapter.toSlice(
|
|
||||||
{ file: html },
|
|
||||||
std.doc,
|
|
||||||
parent,
|
|
||||||
index
|
|
||||||
);
|
|
||||||
return slice;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = dataTransfer.getData('text/plain');
|
|
||||||
const textAdapter = new MarkdownAdapter(job);
|
|
||||||
const slice = await textAdapter.toSlice(
|
|
||||||
{ file: text },
|
|
||||||
std.doc,
|
|
||||||
parent,
|
|
||||||
index
|
|
||||||
);
|
|
||||||
return slice;
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,13 +135,6 @@ export class EdgelessWatcher {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const flavour = selectedElement.flavour;
|
|
||||||
const dragHandleOptions = this.widget.optionRunner.getOption(flavour);
|
|
||||||
if (!dragHandleOptions || !dragHandleOptions.edgeless) {
|
|
||||||
this.widget.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.widget.anchorBlockId.value = selectedElement.id;
|
this.widget.anchorBlockId.value = selectedElement.id;
|
||||||
|
|
||||||
this._showDragHandleOnTopLevelBlocks().catch(console.error);
|
this._showDragHandleOnTopLevelBlocks().catch(console.error);
|
||||||
|
|||||||
-474
@@ -1,474 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import type { NoteBlockModel } from '@blocksuite/affine-model';
|
|
||||||
import {
|
|
||||||
captureEventTarget,
|
|
||||||
findNoteBlockModel,
|
|
||||||
getBlockComponentsExcludeSubtrees,
|
|
||||||
matchFlavours,
|
|
||||||
} from '@blocksuite/affine-shared/utils';
|
|
||||||
import {
|
|
||||||
type BlockComponent,
|
|
||||||
isGfxBlockComponent,
|
|
||||||
type PointerEventState,
|
|
||||||
type UIEventHandler,
|
|
||||||
} from '@blocksuite/block-std';
|
|
||||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
|
||||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
|
||||||
import { Bound, Point } from '@blocksuite/global/utils';
|
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
|
||||||
import { render } from 'lit';
|
|
||||||
|
|
||||||
import type { EdgelessRootBlockComponent } from '../../../edgeless/index.js';
|
|
||||||
import { addNoteAtPoint } from '../../../edgeless/utils/common.js';
|
|
||||||
import { DropIndicator } from '../components/drop-indicator.js';
|
|
||||||
import { AFFINE_DRAG_HANDLE_WIDGET } from '../consts.js';
|
|
||||||
import type { AffineDragHandleWidget } from '../drag-handle.js';
|
|
||||||
import {
|
|
||||||
containBlock,
|
|
||||||
getDuplicateBlocks,
|
|
||||||
includeTextSelection,
|
|
||||||
} from '../utils.js';
|
|
||||||
|
|
||||||
export class LegacyDragEventWatcher {
|
|
||||||
private readonly _changeCursorToGrabbing = () => {
|
|
||||||
document.documentElement.classList.add('affine-drag-preview-grabbing');
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _createDropIndicator = () => {
|
|
||||||
if (!this.widget.dropIndicator) {
|
|
||||||
this.widget.dropIndicator = new DropIndicator();
|
|
||||||
this.widget.rootComponent.append(this.widget.dropIndicator);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When drag end, should move blocks to drop position
|
|
||||||
*/
|
|
||||||
private readonly _dragEndHandler: UIEventHandler = ctx => {
|
|
||||||
this.widget.clearRaf();
|
|
||||||
if (!this.widget.dragging || !this.widget.dragPreview) return false;
|
|
||||||
if (this.widget.draggingElements.length === 0 || this.widget.doc.readonly) {
|
|
||||||
this.widget.hide(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = ctx.get('pointerState');
|
|
||||||
const { target } = state.raw;
|
|
||||||
if (!this.widget.host.contains(target as Node)) {
|
|
||||||
this.widget.hide(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const option of this.widget.optionRunner.options) {
|
|
||||||
if (
|
|
||||||
option.onDragEnd?.({
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
state,
|
|
||||||
draggingElements: this.widget.draggingElements,
|
|
||||||
dropBlockId: this.widget.dropBlockId,
|
|
||||||
dropType: this.widget.dropType,
|
|
||||||
dragPreview: this.widget.dragPreview,
|
|
||||||
noteScale: this.widget.noteScale.peek(),
|
|
||||||
editorHost: this.widget.host,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
this.widget.hide(true);
|
|
||||||
if (this.widget.mode === 'edgeless') {
|
|
||||||
this.widget.edgelessWatcher.checkTopLevelBlockSelection();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call default drag end handler if no option return true
|
|
||||||
this._onDragEnd(state);
|
|
||||||
|
|
||||||
if (this.widget.mode === 'edgeless') {
|
|
||||||
this.widget.edgelessWatcher.checkTopLevelBlockSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When dragging, should:
|
|
||||||
* Update drag preview position
|
|
||||||
* Update indicator position
|
|
||||||
* Update drop block id
|
|
||||||
*/
|
|
||||||
private readonly _dragMoveHandler: UIEventHandler = ctx => {
|
|
||||||
if (
|
|
||||||
this.widget.isHoverDragHandleVisible ||
|
|
||||||
this.widget.isTopLevelDragHandleVisible
|
|
||||||
) {
|
|
||||||
this.widget.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.widget.dragging || this.widget.draggingElements.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.get('defaultState').event.preventDefault();
|
|
||||||
const state = ctx.get('pointerState');
|
|
||||||
|
|
||||||
for (const option of this.widget.optionRunner.options) {
|
|
||||||
if (
|
|
||||||
option.onDragMove?.({
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
state,
|
|
||||||
draggingElements: this.widget.draggingElements,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call default drag move handler if no option return true
|
|
||||||
return this._onDragMove(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When start dragging, should set dragging elements and create drag preview
|
|
||||||
*/
|
|
||||||
private readonly _dragStartHandler: UIEventHandler = ctx => {
|
|
||||||
const state = ctx.get('pointerState');
|
|
||||||
// If not click left button to start dragging, should do nothing
|
|
||||||
const { button } = state.raw;
|
|
||||||
if (button !== 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// call default drag start handler if no option return true
|
|
||||||
for (const option of this.widget.optionRunner.options) {
|
|
||||||
if (
|
|
||||||
option.onDragStart?.({
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
state,
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
startDragging: this._startDragging,
|
|
||||||
anchorBlockId: this.widget.anchorBlockId.peek() ?? '',
|
|
||||||
editorHost: this.widget.host,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this._onDragStart(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _onDragEnd = (state: PointerEventState) => {
|
|
||||||
const targetBlockId = this.widget.dropBlockId;
|
|
||||||
const dropType = this.widget.dropType;
|
|
||||||
const draggingElements = this.widget.draggingElements;
|
|
||||||
this.widget.hide(true);
|
|
||||||
|
|
||||||
// handle drop of blocks from note onto edgeless container
|
|
||||||
if (!targetBlockId) {
|
|
||||||
const target = captureEventTarget(state.raw.target);
|
|
||||||
if (!target) return false;
|
|
||||||
|
|
||||||
const isTargetEdgelessContainer =
|
|
||||||
target.classList.contains('edgeless-container');
|
|
||||||
if (!isTargetEdgelessContainer) return false;
|
|
||||||
|
|
||||||
const selectedBlocks = getBlockComponentsExcludeSubtrees(draggingElements)
|
|
||||||
.map(element => element.model)
|
|
||||||
.filter((x): x is BlockModel => !!x);
|
|
||||||
if (selectedBlocks.length === 0) return false;
|
|
||||||
|
|
||||||
const isSurfaceComponent = selectedBlocks.some(block => {
|
|
||||||
const parent = this.widget.doc.getParent(block.id);
|
|
||||||
return matchFlavours(parent, ['affine:surface']);
|
|
||||||
});
|
|
||||||
if (isSurfaceComponent) return true;
|
|
||||||
|
|
||||||
const edgelessRoot = this.widget
|
|
||||||
.rootComponent as EdgelessRootBlockComponent;
|
|
||||||
|
|
||||||
const { left: viewportLeft, top: viewportTop } = edgelessRoot.viewport;
|
|
||||||
|
|
||||||
const newNoteId = addNoteAtPoint(
|
|
||||||
edgelessRoot.std,
|
|
||||||
new Point(state.raw.x - viewportLeft, state.raw.y - viewportTop),
|
|
||||||
{
|
|
||||||
scale: this.widget.noteScale.peek(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const newNoteBlock = this.widget.doc.getBlockById(
|
|
||||||
newNoteId
|
|
||||||
) as NoteBlockModel;
|
|
||||||
if (!newNoteBlock) return;
|
|
||||||
|
|
||||||
const bound = Bound.deserialize(newNoteBlock.xywh);
|
|
||||||
bound.h *= this.widget.noteScale.peek();
|
|
||||||
bound.w *= this.widget.noteScale.peek();
|
|
||||||
this.widget.doc.updateBlock(newNoteBlock, {
|
|
||||||
xywh: bound.serialize(),
|
|
||||||
edgeless: {
|
|
||||||
...newNoteBlock.edgeless,
|
|
||||||
scale: this.widget.noteScale.peek(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const altKey = state.raw.altKey;
|
|
||||||
if (altKey) {
|
|
||||||
const duplicateBlocks = getDuplicateBlocks(selectedBlocks);
|
|
||||||
|
|
||||||
this.widget.doc.addBlocks(duplicateBlocks, newNoteBlock);
|
|
||||||
} else {
|
|
||||||
this.widget.doc.moveBlocks(selectedBlocks, newNoteBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
edgelessRoot.service.selection.set({
|
|
||||||
elements: [newNoteBlock.id],
|
|
||||||
editing: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should make sure drop block id is not in selected blocks
|
|
||||||
if (
|
|
||||||
containBlock(this.widget.selectionHelper.selectedBlockIds, targetBlockId)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedBlocks = getBlockComponentsExcludeSubtrees(draggingElements)
|
|
||||||
.map(element => element.model)
|
|
||||||
.filter((x): x is BlockModel => !!x);
|
|
||||||
if (!selectedBlocks.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetBlock = this.widget.doc.getBlockById(targetBlockId);
|
|
||||||
if (!targetBlock) return;
|
|
||||||
|
|
||||||
const shouldInsertIn = dropType === 'in';
|
|
||||||
|
|
||||||
const parent = shouldInsertIn
|
|
||||||
? targetBlock
|
|
||||||
: this.widget.doc.getParent(targetBlockId);
|
|
||||||
if (!parent) return;
|
|
||||||
|
|
||||||
const altKey = state.raw.altKey;
|
|
||||||
|
|
||||||
if (shouldInsertIn) {
|
|
||||||
if (altKey) {
|
|
||||||
const duplicateBlocks = getDuplicateBlocks(selectedBlocks);
|
|
||||||
|
|
||||||
this.widget.doc.addBlocks(duplicateBlocks, targetBlock);
|
|
||||||
} else {
|
|
||||||
this.widget.doc.moveBlocks(selectedBlocks, targetBlock);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (altKey) {
|
|
||||||
const duplicateBlocks = getDuplicateBlocks(selectedBlocks);
|
|
||||||
|
|
||||||
const parentIndex =
|
|
||||||
parent.children.indexOf(targetBlock) + (dropType === 'after' ? 1 : 0);
|
|
||||||
|
|
||||||
this.widget.doc.addBlocks(duplicateBlocks, parent, parentIndex);
|
|
||||||
} else {
|
|
||||||
this.widget.doc.moveBlocks(
|
|
||||||
selectedBlocks,
|
|
||||||
parent,
|
|
||||||
targetBlock,
|
|
||||||
dropType === 'before'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: need a better way to update selection
|
|
||||||
// Should update selection after moving blocks
|
|
||||||
// In doc page mode, update selected blocks
|
|
||||||
// In edgeless mode, focus on the first block
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!parent) return;
|
|
||||||
// Need to update selection when moving blocks successfully
|
|
||||||
// Because the block path may be changed after moving
|
|
||||||
const parentElement = this.widget.std.view.getBlock(parent.id);
|
|
||||||
if (parentElement) {
|
|
||||||
const newSelectedBlocks = selectedBlocks.map(block => {
|
|
||||||
return this.widget.std.view.getBlock(block.id);
|
|
||||||
});
|
|
||||||
if (!newSelectedBlocks) return;
|
|
||||||
|
|
||||||
const note = findNoteBlockModel(parentElement.model);
|
|
||||||
if (!note) return;
|
|
||||||
this.widget.selectionHelper.setSelectedBlocks(
|
|
||||||
newSelectedBlocks as BlockComponent[],
|
|
||||||
note.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _onDragMove = (state: PointerEventState) => {
|
|
||||||
this.widget.clearRaf();
|
|
||||||
|
|
||||||
this.widget.rafID = requestAnimationFrame(() => {
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
this.widget.edgelessWatcher.updateDragPreviewPosition(state);
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
this.widget.updateDropIndicator(state, true);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _onDragStart = (state: PointerEventState) => {
|
|
||||||
// Get current hover block element by path
|
|
||||||
const hoverBlock = this.widget.anchorBlockComponent.peek();
|
|
||||||
if (!hoverBlock) return false;
|
|
||||||
|
|
||||||
const element = captureEventTarget(state.raw.target);
|
|
||||||
const dragByHandle = !!element?.closest(AFFINE_DRAG_HANDLE_WIDGET);
|
|
||||||
const isInSurface = isGfxBlockComponent(hoverBlock);
|
|
||||||
|
|
||||||
if (isInSurface && dragByHandle) {
|
|
||||||
const viewport = this.widget.std.get(GfxControllerIdentifier).viewport;
|
|
||||||
const zoom = viewport.zoom ?? 1;
|
|
||||||
const dragPreviewEl = document.createElement('div');
|
|
||||||
const bound = Bound.deserialize(hoverBlock.model.xywh);
|
|
||||||
const offset = new Point(bound.x * zoom, bound.y * zoom);
|
|
||||||
|
|
||||||
// TODO: not use `dangerouslyRenderModel` to render drag preview
|
|
||||||
render(
|
|
||||||
this.widget.std.host.dangerouslyRenderModel(hoverBlock.model),
|
|
||||||
dragPreviewEl
|
|
||||||
);
|
|
||||||
|
|
||||||
this._startDragging([hoverBlock], state, dragPreviewEl, offset);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectBlockAndStartDragging = () => {
|
|
||||||
this.widget.std.selection.setGroup('note', [
|
|
||||||
this.widget.std.selection.create('block', {
|
|
||||||
blockId: hoverBlock.blockId,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
this._startDragging([hoverBlock], state);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.widget.draggingElements.length === 0) {
|
|
||||||
const dragByBlock =
|
|
||||||
hoverBlock.contains(element) && !hoverBlock.model.text;
|
|
||||||
|
|
||||||
const canDragByBlock =
|
|
||||||
matchFlavours(hoverBlock.model, [
|
|
||||||
'affine:attachment',
|
|
||||||
'affine:bookmark',
|
|
||||||
]) || hoverBlock.model.flavour.startsWith('affine:embed-');
|
|
||||||
|
|
||||||
if (!isInSurface && dragByBlock && canDragByBlock) {
|
|
||||||
selectBlockAndStartDragging();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should only start dragging when pointer down on drag handle
|
|
||||||
// And current mouse button is left button
|
|
||||||
if (!dragByHandle) {
|
|
||||||
this.widget.hide();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.widget.draggingElements.length === 1 && !isInSurface) {
|
|
||||||
selectBlockAndStartDragging();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.widget.isHoverDragHandleVisible) return false;
|
|
||||||
|
|
||||||
let selections = this.widget.selectionHelper.selectedBlocks;
|
|
||||||
|
|
||||||
// When current selection is TextSelection
|
|
||||||
// Should set BlockSelection for the blocks in native range
|
|
||||||
if (selections.length > 0 && includeTextSelection(selections)) {
|
|
||||||
const nativeSelection = document.getSelection();
|
|
||||||
const rangeManager = this.widget.std.range;
|
|
||||||
if (nativeSelection && nativeSelection.rangeCount > 0 && rangeManager) {
|
|
||||||
const range = nativeSelection.getRangeAt(0);
|
|
||||||
const blocks = rangeManager.getSelectedBlockComponentsByRange(range, {
|
|
||||||
match: el => el.model.role === 'content',
|
|
||||||
mode: 'highest',
|
|
||||||
});
|
|
||||||
this.widget.selectionHelper.setSelectedBlocks(blocks);
|
|
||||||
selections = this.widget.selectionHelper.selectedBlocks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When there is no selected blocks
|
|
||||||
// Or selected blocks not including current hover block
|
|
||||||
// Set current hover block as selected
|
|
||||||
if (
|
|
||||||
selections.length === 0 ||
|
|
||||||
!containBlock(
|
|
||||||
selections.map(selection => selection.blockId),
|
|
||||||
this.widget.anchorBlockId.peek()!
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const block = this.widget.anchorBlockComponent.peek();
|
|
||||||
if (block) {
|
|
||||||
this.widget.selectionHelper.setSelectedBlocks([block]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const blocks = this.widget.selectionHelper.selectedBlockComponents;
|
|
||||||
|
|
||||||
// This could be skip if we can ensure that all selected blocks are on the same level
|
|
||||||
// Which means not selecting parent block and child block at the same time
|
|
||||||
const blocksExcludingChildren = getBlockComponentsExcludeSubtrees(
|
|
||||||
blocks
|
|
||||||
) as BlockComponent[];
|
|
||||||
|
|
||||||
if (blocksExcludingChildren.length === 0) return false;
|
|
||||||
|
|
||||||
this._startDragging(blocksExcludingChildren, state);
|
|
||||||
this.widget.hide();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _startDragging = (
|
|
||||||
blocks: BlockComponent[],
|
|
||||||
state: PointerEventState,
|
|
||||||
dragPreviewEl?: HTMLElement,
|
|
||||||
dragPreviewOffset?: Point
|
|
||||||
) => {
|
|
||||||
if (!blocks.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.widget.draggingElements = blocks;
|
|
||||||
|
|
||||||
this.widget.dragPreview = this.widget.previewHelper.createDragPreview(
|
|
||||||
blocks,
|
|
||||||
// @ts-expect-error FIXME: ts error
|
|
||||||
state,
|
|
||||||
dragPreviewEl,
|
|
||||||
dragPreviewOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
this.widget.dragging = true;
|
|
||||||
this._changeCursorToGrabbing();
|
|
||||||
this._createDropIndicator();
|
|
||||||
this.widget.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(readonly widget: AffineDragHandleWidget) {}
|
|
||||||
|
|
||||||
watch() {
|
|
||||||
this.widget.disposables.addFromEvent(this.widget, 'pointerdown', e => {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IS_MOBILE) return;
|
|
||||||
|
|
||||||
this.widget.handleEvent('dragStart', this._dragStartHandler);
|
|
||||||
this.widget.handleEvent('dragMove', this._dragMoveHandler);
|
|
||||||
this.widget.handleEvent('dragEnd', this._dragEndHandler, { global: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user