feat(core): dnd support external types (#9033)
fix AF-1847 two issues: 1. original `dropTargetForExternal` only works if dragging target is from another window context. patched the library to bypass this issue 2. `dataTransfer`'s content is only available on `drop` event. This means we cannot have `canDrop` checks for external elements (like blocksuite links).
This commit is contained in:
14
packages/frontend/core/src/modules/dnd/index.ts
Normal file
14
packages/frontend/core/src/modules/dnd/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
DocsService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { DndService } from './services';
|
||||
|
||||
export function configureDndModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(DndService, [DocsService, WorkspaceService]);
|
||||
}
|
||||
89
packages/frontend/core/src/modules/dnd/services/index.ts
Normal file
89
packages/frontend/core/src/modules/dnd/services/index.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { ExternalGetDataFeedbackArgs } from '@affine/component';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import type { DocsService, WorkspaceService } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { resolveLinkToDoc } from '../../navigation';
|
||||
|
||||
type EntityResolver = (
|
||||
data: string
|
||||
) => AffineDNDData['draggable']['entity'] | null;
|
||||
|
||||
export class DndService extends Service {
|
||||
constructor(
|
||||
private readonly docsService: DocsService,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
|
||||
// order matters
|
||||
this.resolvers.set('text/html', this.resolveHTML);
|
||||
this.resolvers.set('text/uri-list', this.resolveUriList);
|
||||
}
|
||||
|
||||
private readonly resolvers = new Map<string, EntityResolver>();
|
||||
|
||||
externalDataAdapter = (args: ExternalGetDataFeedbackArgs) => {
|
||||
const from: AffineDNDData['draggable']['from'] = {
|
||||
at: 'external',
|
||||
};
|
||||
let entity: AffineDNDData['draggable']['entity'];
|
||||
|
||||
// in the order of the resolvers instead of the order of the types
|
||||
for (const [type, resolver] of this.resolvers) {
|
||||
if (args.source.types.includes(type)) {
|
||||
const stringData = args.source.getStringData(type);
|
||||
if (stringData) {
|
||||
const candidate = resolver(stringData);
|
||||
if (candidate) {
|
||||
entity = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
entity,
|
||||
};
|
||||
};
|
||||
|
||||
private readonly resolveUriList: EntityResolver = urls => {
|
||||
// only deal with the first url
|
||||
const url = urls
|
||||
?.split('\n')
|
||||
.filter(u => u.trim() && !u.trim().startsWith('#'))[0];
|
||||
|
||||
if (url) {
|
||||
const maybeDocLink = resolveLinkToDoc(url);
|
||||
|
||||
// check if the doc is in the current workspace
|
||||
if (
|
||||
maybeDocLink?.workspaceId === this.workspaceService.workspace.id &&
|
||||
this.docsService.list.doc$(maybeDocLink.docId).value &&
|
||||
// skip for block references for now
|
||||
!maybeDocLink.blockIds?.length
|
||||
) {
|
||||
return {
|
||||
type: 'doc',
|
||||
id: maybeDocLink.docId,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// todo: implement this
|
||||
private readonly resolveHTML: EntityResolver = _html => {
|
||||
try {
|
||||
// const parser = new DOMParser();
|
||||
// const doc = parser.parseFromString(html, 'text/html');
|
||||
// return doc.body.innerText;
|
||||
} catch {
|
||||
// ignore the error
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user