mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 00:56:26 +08:00
feat(editor): edgeless clipboard config extension (#11168)
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { addAttachments } from '@blocksuite/affine-block-attachment';
|
||||
import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame';
|
||||
import { addImages } from '@blocksuite/affine-block-image';
|
||||
import {
|
||||
CanvasElementType,
|
||||
type ClipboardConfigCreationContext,
|
||||
EdgelessClipboardConfigIdentifier,
|
||||
EdgelessCRUDIdentifier,
|
||||
ExportManager,
|
||||
getSurfaceComponent,
|
||||
SurfaceGroupLikeModel,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
@@ -14,7 +18,6 @@ import {
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
FrameBlockModel,
|
||||
MAX_IMAGE_WIDTH,
|
||||
ReferenceInfoSchema,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
CANVAS_EXPORT_IGNORE_TAGS,
|
||||
@@ -34,6 +37,7 @@ import {
|
||||
referenceToNode,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type {
|
||||
BlockComponent,
|
||||
BlockStdScope,
|
||||
EditorHost,
|
||||
SurfaceSelection,
|
||||
@@ -63,7 +67,6 @@ import { assertType } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type BlockSnapshot,
|
||||
BlockSnapshotSchema,
|
||||
fromJSON,
|
||||
type SliceSnapshot,
|
||||
} from '@blocksuite/store';
|
||||
import DOMPurify from 'dompurify';
|
||||
@@ -75,15 +78,12 @@ import {
|
||||
decodeClipboardBlobs,
|
||||
encodeClipboardBlobs,
|
||||
} from '../../clipboard/utils.js';
|
||||
import type { RootBlockComponent } from '../../types.js';
|
||||
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
||||
import { edgelessElementsBoundFromRawData } from '../utils/bound-utils.js';
|
||||
import { createNewPresentationIndexes } from '../utils/clipboard-utils.js';
|
||||
import {
|
||||
getSortedCloneElements,
|
||||
serializeElement,
|
||||
} from '../utils/clone-utils.js';
|
||||
import { deleteElements } from '../utils/crud.js';
|
||||
import {
|
||||
isAttachmentBlock,
|
||||
isCanvasElementWithText,
|
||||
@@ -95,41 +95,13 @@ const BLOCKSUITE_SURFACE = 'blocksuite/surface';
|
||||
const { GROUP, MINDMAP, CONNECTOR } = CanvasElementType;
|
||||
const IMAGE_PADDING = 5; // for rotated shapes some padding is needed
|
||||
|
||||
type CreationContext = {
|
||||
/**
|
||||
* element old id to new id
|
||||
*/
|
||||
oldToNewIdMap: Map<string, string>;
|
||||
/**
|
||||
* element old id to new layer index
|
||||
*/
|
||||
originalIndexes: Map<string, string>;
|
||||
|
||||
/**
|
||||
* frame old id to new presentation index
|
||||
*/
|
||||
newPresentationIndexes: Map<string, string>;
|
||||
};
|
||||
|
||||
type BlockCreationFunction = (
|
||||
snapshot: BlockSnapshot,
|
||||
context: CreationContext
|
||||
) => Promise<string | null> | string | null; // new Id
|
||||
|
||||
interface CanvasExportOptions {
|
||||
dpr?: number;
|
||||
padding?: number;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
interface BlockConfig {
|
||||
flavour: string;
|
||||
createFunction: BlockCreationFunction;
|
||||
}
|
||||
|
||||
export class EdgelessClipboardController extends PageClipboard {
|
||||
private readonly _blockConfigs: BlockConfig[] = [];
|
||||
|
||||
private readonly _initEdgelessClipboard = () => {
|
||||
this.host.handleEvent(
|
||||
'copy',
|
||||
@@ -210,7 +182,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
this.selectionManager.selectedElements
|
||||
);
|
||||
this.doc.transact(() => {
|
||||
deleteElements(this.edgeless, elements);
|
||||
this.crud.deleteElements(elements);
|
||||
});
|
||||
|
||||
this.selectionManager.set({
|
||||
@@ -241,6 +213,8 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
const data = event.clipboardData;
|
||||
if (!data) return;
|
||||
|
||||
if (!this.surface) return;
|
||||
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
const point: IVec = [lastMousePos.x, lastMousePos.y];
|
||||
|
||||
@@ -283,7 +257,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
if (isUrlInClipboard(data)) {
|
||||
const url = data.getData('text/plain');
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
const [x, y] = this.host.service.viewport.toModelCoord(
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
lastMousePos.x,
|
||||
lastMousePos.y
|
||||
);
|
||||
@@ -407,7 +381,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
|
||||
private get selectionManager() {
|
||||
return this.std.get(GfxControllerIdentifier).selection;
|
||||
return this.gfx.selection;
|
||||
}
|
||||
|
||||
private get std() {
|
||||
@@ -415,7 +389,15 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
|
||||
private get surface() {
|
||||
return this.host.surface;
|
||||
return getSurfaceComponent(this.std);
|
||||
}
|
||||
|
||||
private get frame() {
|
||||
return this.std.get(EdgelessFrameManagerIdentifier);
|
||||
}
|
||||
|
||||
private get gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
private get crud() {
|
||||
@@ -423,36 +405,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
|
||||
private get toolManager() {
|
||||
return this.host.gfx.tool;
|
||||
}
|
||||
|
||||
constructor(public override host: EdgelessRootBlockComponent) {
|
||||
super(host);
|
||||
// Register existing block creation functions
|
||||
this.registerBlock('affine:note', this._createNoteBlock);
|
||||
this.registerBlock('affine:edgeless-text', this._createEdgelessTextBlock);
|
||||
this.registerBlock('affine:image', this._createImageBlock);
|
||||
this.registerBlock('affine:frame', this._createFrameBlock);
|
||||
this.registerBlock('affine:attachment', this._createAttachmentBlock);
|
||||
|
||||
// external links
|
||||
this.registerBlock('affine:bookmark', this._createBookmarkBlock);
|
||||
this.registerBlock('affine:embed-figma', this._createFigmaEmbedBlock);
|
||||
this.registerBlock('affine:embed-github', this._createGithubEmbedBlock);
|
||||
this.registerBlock('affine:embed-html', this._createHtmlEmbedBlock);
|
||||
this.registerBlock('affine:embed-loom', this._createLoomEmbedBlock);
|
||||
this.registerBlock('affine:embed-youtube', this._createYoutubeEmbedBlock);
|
||||
this.registerBlock('affine:embed-iframe', this._createIframeEmbedBlock);
|
||||
|
||||
// internal links
|
||||
this.registerBlock(
|
||||
'affine:embed-linked-doc',
|
||||
this._createLinkedDocEmbedBlock
|
||||
);
|
||||
this.registerBlock(
|
||||
'affine:embed-synced-doc',
|
||||
this._createSyncedDocEmbedBlock
|
||||
);
|
||||
return this.gfx.tool;
|
||||
}
|
||||
|
||||
private _checkCanContinueToCanvas(
|
||||
@@ -468,54 +421,9 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private async _createAttachmentBlock(attachment: BlockSnapshot) {
|
||||
const { xywh, rotate, sourceId, name, size, type, embed, style } =
|
||||
attachment.props;
|
||||
|
||||
if (!(await this.host.std.workspace.blobSync.get(sourceId as string))) {
|
||||
return null;
|
||||
}
|
||||
const attachmentId = this.crud.addBlock(
|
||||
'affine:attachment',
|
||||
{
|
||||
xywh,
|
||||
rotate,
|
||||
sourceId,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
embed,
|
||||
style,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return attachmentId;
|
||||
}
|
||||
|
||||
private _createBookmarkBlock(bookmark: BlockSnapshot) {
|
||||
const { xywh, style, url, caption, description, icon, image, title } =
|
||||
bookmark.props;
|
||||
|
||||
const bookmarkId = this.crud.addBlock(
|
||||
'affine:bookmark',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
description,
|
||||
icon,
|
||||
image,
|
||||
title,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return bookmarkId;
|
||||
}
|
||||
|
||||
private _createCanvasElement(
|
||||
clipboardData: SerializedElement,
|
||||
context: CreationContext,
|
||||
context: ClipboardConfigCreationContext,
|
||||
newXYWH: SerializedXYWH
|
||||
): GfxPrimitiveElementModel | null {
|
||||
if (clipboardData.type === GROUP) {
|
||||
@@ -614,307 +522,8 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
return element;
|
||||
}
|
||||
|
||||
private async _createEdgelessTextBlock(edgelessText: BlockSnapshot) {
|
||||
const oldId = edgelessText.id;
|
||||
delete edgelessText.props.index;
|
||||
if (!edgelessText.props.xywh) {
|
||||
console.error(
|
||||
`EdgelessText block(id: ${oldId}) does not have xywh property`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
const newId = await this.onBlockSnapshotPaste(
|
||||
edgelessText,
|
||||
this.doc,
|
||||
this.edgeless.surface.model.id
|
||||
);
|
||||
if (!newId) {
|
||||
console.error(`Failed to paste EdgelessText block(id: ${oldId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
private _createFigmaEmbedBlock(figmaEmbed: BlockSnapshot) {
|
||||
const { xywh, style, url, caption, title, description } = figmaEmbed.props;
|
||||
|
||||
const embedFigmaId = this.crud.addBlock(
|
||||
'affine:embed-figma',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedFigmaId;
|
||||
}
|
||||
|
||||
private _createFrameBlock(frame: BlockSnapshot, context: CreationContext) {
|
||||
const { oldToNewIdMap, newPresentationIndexes } = context;
|
||||
const { xywh, title, background, childElementIds } = frame.props;
|
||||
|
||||
const newChildElementIds: Record<string, boolean> = {};
|
||||
|
||||
if (typeof childElementIds === 'object' && childElementIds !== null) {
|
||||
Object.keys(childElementIds).forEach(oldId => {
|
||||
const newId = oldToNewIdMap.get(oldId);
|
||||
if (newId) {
|
||||
newChildElementIds[newId] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const frameId = this.crud.addBlock(
|
||||
'affine:frame',
|
||||
{
|
||||
xywh,
|
||||
background,
|
||||
title: fromJSON(title),
|
||||
childElementIds: newChildElementIds,
|
||||
presentationIndex: newPresentationIndexes.get(frame.id),
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return frameId;
|
||||
}
|
||||
|
||||
private _createGithubEmbedBlock(githubEmbed: BlockSnapshot) {
|
||||
const {
|
||||
xywh,
|
||||
style,
|
||||
owner,
|
||||
repo,
|
||||
githubType,
|
||||
githubId,
|
||||
url,
|
||||
caption,
|
||||
image,
|
||||
status,
|
||||
statusReason,
|
||||
title,
|
||||
description,
|
||||
createdAt,
|
||||
assignees,
|
||||
} = githubEmbed.props;
|
||||
|
||||
const embedGithubId = this.crud.addBlock(
|
||||
'affine:embed-github',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
owner,
|
||||
repo,
|
||||
githubType,
|
||||
githubId,
|
||||
url,
|
||||
caption,
|
||||
image,
|
||||
status,
|
||||
statusReason,
|
||||
title,
|
||||
description,
|
||||
createdAt,
|
||||
assignees,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedGithubId;
|
||||
}
|
||||
|
||||
private _createHtmlEmbedBlock(htmlEmbed: BlockSnapshot) {
|
||||
const { xywh, style, caption, html, design } = htmlEmbed.props;
|
||||
|
||||
const embedHtmlId = this.crud.addBlock(
|
||||
'affine:embed-html',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
html,
|
||||
design,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedHtmlId;
|
||||
}
|
||||
|
||||
private async _createImageBlock(image: BlockSnapshot) {
|
||||
const { xywh, rotate, sourceId, size, width, height, caption } =
|
||||
image.props;
|
||||
|
||||
if (!(await this.host.std.workspace.blobSync.get(sourceId as string))) {
|
||||
return null;
|
||||
}
|
||||
return this.crud.addBlock(
|
||||
'affine:image',
|
||||
{
|
||||
caption,
|
||||
sourceId,
|
||||
xywh,
|
||||
rotate,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
|
||||
private _createLinkedDocEmbedBlock(linkedDocEmbed: BlockSnapshot) {
|
||||
const { xywh, style, caption, pageId, params, title, description } =
|
||||
linkedDocEmbed.props;
|
||||
const referenceInfo = ReferenceInfoSchema.parse({
|
||||
pageId,
|
||||
params,
|
||||
title,
|
||||
description,
|
||||
});
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
...referenceInfo,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
|
||||
private _createLoomEmbedBlock(loomEmbed: BlockSnapshot) {
|
||||
const { xywh, style, url, caption, videoId, image, title, description } =
|
||||
loomEmbed.props;
|
||||
|
||||
const embedLoomId = this.crud.addBlock(
|
||||
'affine:embed-loom',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedLoomId;
|
||||
}
|
||||
|
||||
private async _createNoteBlock(note: BlockSnapshot) {
|
||||
const oldId = note.id;
|
||||
|
||||
delete note.props.index;
|
||||
if (!note.props.xywh) {
|
||||
console.error(`Note block(id: ${oldId}) does not have xywh property`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const newId = await this.onBlockSnapshotPaste(
|
||||
note,
|
||||
this.doc,
|
||||
this.doc.root!.id
|
||||
);
|
||||
if (!newId) {
|
||||
console.error(`Failed to paste note block(id: ${oldId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
private _createSyncedDocEmbedBlock(syncedDocEmbed: BlockSnapshot) {
|
||||
const { xywh, style, caption, scale, pageId, params } =
|
||||
syncedDocEmbed.props;
|
||||
const referenceInfo = ReferenceInfoSchema.parse({ pageId, params });
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
scale,
|
||||
...referenceInfo,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
|
||||
private _createYoutubeEmbedBlock(youtubeEmbed: BlockSnapshot) {
|
||||
const {
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
creator,
|
||||
creatorUrl,
|
||||
creatorImage,
|
||||
} = youtubeEmbed.props;
|
||||
|
||||
const embedYoutubeId = this.crud.addBlock(
|
||||
'affine:embed-youtube',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
creator,
|
||||
creatorUrl,
|
||||
creatorImage,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedYoutubeId;
|
||||
}
|
||||
|
||||
private _createIframeEmbedBlock(embedIframe: BlockSnapshot) {
|
||||
const {
|
||||
xywh,
|
||||
caption,
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
iframeUrl,
|
||||
scale,
|
||||
width,
|
||||
height,
|
||||
} = embedIframe.props;
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-iframe',
|
||||
{
|
||||
url,
|
||||
iframeUrl,
|
||||
xywh,
|
||||
caption,
|
||||
title,
|
||||
description,
|
||||
scale,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
|
||||
private async _edgelessToCanvas(
|
||||
edgeless: EdgelessRootBlockComponent,
|
||||
edgeless: BlockComponent,
|
||||
bound: IBound,
|
||||
nodes?: GfxBlockElementModel[],
|
||||
canvasElements: GfxPrimitiveElementModel[] = [],
|
||||
@@ -930,18 +539,11 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
|
||||
const html2canvas = (await import('html2canvas')).default;
|
||||
if (!(html2canvas instanceof Function)) return;
|
||||
if (!this.surface) return;
|
||||
|
||||
const pathname = location.pathname;
|
||||
const editorMode = isInsidePageEditor(host);
|
||||
|
||||
const rootComponent = getRootByEditorHost(host);
|
||||
if (!rootComponent) return;
|
||||
|
||||
const container = rootComponent.querySelector(
|
||||
'.affine-block-children-container'
|
||||
);
|
||||
if (!container) return;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = (bound.w + padding * 2) * dpr;
|
||||
canvas.height = (bound.h + padding * 2) * dpr;
|
||||
@@ -1024,7 +626,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
|
||||
const nodeElements =
|
||||
nodes ??
|
||||
(edgeless.service.gfx.getElementsByBound(bound, {
|
||||
(this.gfx.getElementsByBound(bound, {
|
||||
type: 'block',
|
||||
}) as GfxBlockElementModel[]);
|
||||
for (const nodeElement of nodeElements) {
|
||||
@@ -1032,15 +634,13 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
|
||||
if (matchModels(nodeElement, [FrameBlockModel])) {
|
||||
const blocksInsideFrame: GfxBlockElementModel[] = [];
|
||||
this.edgeless.service.frame
|
||||
.getElementsInFrameBound(nodeElement, false)
|
||||
.forEach(ele => {
|
||||
if (isTopLevelBlock(ele)) {
|
||||
blocksInsideFrame.push(ele as GfxBlockElementModel);
|
||||
} else {
|
||||
canvasElements.push(ele as GfxPrimitiveElementModel);
|
||||
}
|
||||
});
|
||||
this.frame.getElementsInFrameBound(nodeElement, false).forEach(ele => {
|
||||
if (isTopLevelBlock(ele)) {
|
||||
blocksInsideFrame.push(ele as GfxBlockElementModel);
|
||||
} else {
|
||||
canvasElements.push(ele as GfxPrimitiveElementModel);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < blocksInsideFrame.length; i++) {
|
||||
@@ -1052,7 +652,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
this._checkCanContinueToCanvas(host, pathname, editorMode);
|
||||
}
|
||||
|
||||
const surfaceCanvas = edgeless.surface.renderer.getCanvasByBound(
|
||||
const surfaceCanvas = this.surface.renderer.getCanvasByBound(
|
||||
bound,
|
||||
canvasElements
|
||||
);
|
||||
@@ -1114,9 +714,8 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
|
||||
private async _pasteTextContentAsNote(content: BlockSnapshot[] | string) {
|
||||
const edgeless = this.host;
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
const [x, y] = edgeless.service.viewport.toModelCoord(
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
lastMousePos.x,
|
||||
lastMousePos.y
|
||||
);
|
||||
@@ -1167,11 +766,11 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
edgeless.service.selection.set({
|
||||
this.gfx.selection.set({
|
||||
elements: [noteId],
|
||||
editing: false,
|
||||
});
|
||||
edgeless.gfx.tool.setTool('default');
|
||||
this.gfx.tool.setTool('default');
|
||||
}
|
||||
|
||||
private _replaceRichTextWithSvgElement(element: HTMLElement) {
|
||||
@@ -1221,7 +820,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
const idxGenerator = this.edgeless.service.layer.createIndexGenerator();
|
||||
const idxGenerator = this.gfx.layer.createIndexGenerator();
|
||||
const sortedElements = elements.sort(compare);
|
||||
sortedElements.forEach(ele => {
|
||||
const newIndex = idxGenerator();
|
||||
@@ -1250,7 +849,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
pasteCenter =
|
||||
pasteCenter ??
|
||||
this.host.service.viewport.toModelCoord(lastMousePos.x, lastMousePos.y);
|
||||
this.gfx.viewport.toModelCoord(lastMousePos.x, lastMousePos.y);
|
||||
const [modelX, modelY] = pasteCenter;
|
||||
oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData);
|
||||
|
||||
@@ -1270,7 +869,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
|
||||
// create blocks and canvas elements
|
||||
|
||||
const context: CreationContext = {
|
||||
const context: ClipboardConfigCreationContext = {
|
||||
oldToNewIdMap: new Map<string, string>(),
|
||||
originalIndexes: new Map<string, string>(),
|
||||
newPresentationIndexes: createNewPresentationIndexes(
|
||||
@@ -1288,8 +887,8 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
if (blockSnapshot) {
|
||||
const oldId = blockSnapshot.id;
|
||||
|
||||
const config = this._blockConfigs.find(
|
||||
({ flavour }) => flavour === blockSnapshot.flavour
|
||||
const config = this.std.getOptional(
|
||||
EdgelessClipboardConfigIdentifier(blockSnapshot.flavour)
|
||||
);
|
||||
if (!config) continue;
|
||||
|
||||
@@ -1311,7 +910,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
);
|
||||
blockSnapshot.props.lockedBySelf = false;
|
||||
|
||||
const newId = await config.createFunction(blockSnapshot, context);
|
||||
const newId = await config.createBlock(blockSnapshot, context);
|
||||
if (!newId) continue;
|
||||
|
||||
const block = this.doc.getBlock(newId);
|
||||
@@ -1369,13 +968,6 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
this._initEdgelessClipboard();
|
||||
}
|
||||
|
||||
registerBlock(flavour: string, createFunction: BlockCreationFunction) {
|
||||
this._blockConfigs.push({
|
||||
flavour,
|
||||
createFunction: createFunction.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
async toCanvas(
|
||||
blocks: GfxBlockElementModel[],
|
||||
shapes: ShapeElementModel[],
|
||||
@@ -1466,12 +1058,3 @@ function tryGetSvgFromClipboard(clipboardData: DataTransfer) {
|
||||
const file = new File([blob], 'pasted-image.svg', { type: 'image/svg+xml' });
|
||||
return file;
|
||||
}
|
||||
|
||||
function getRootByEditorHost(
|
||||
editorHost: EditorHost
|
||||
): RootBlockComponent | null {
|
||||
const model = editorHost.doc.root;
|
||||
if (!model) return null;
|
||||
const root = editorHost.view.getBlock(model.id);
|
||||
return root as RootBlockComponent | null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
import {
|
||||
type ClipboardConfigCreationContext,
|
||||
EdgelessClipboardConfig,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { ReferenceInfoSchema } from '@blocksuite/affine-model';
|
||||
import { type BlockSnapshot, fromJSON } from '@blocksuite/store';
|
||||
|
||||
export class EdgelessClipboardNoteConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:note';
|
||||
|
||||
override async createBlock(note: BlockSnapshot): Promise<null | string> {
|
||||
const oldId = note.id;
|
||||
|
||||
delete note.props.index;
|
||||
if (!note.props.xywh) {
|
||||
console.error(`Note block(id: ${oldId}) does not have xywh property`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const newId = await this.onBlockSnapshotPaste(
|
||||
note,
|
||||
this.std.store,
|
||||
this.std.store.root!.id
|
||||
);
|
||||
if (!newId) {
|
||||
console.error(`Failed to paste note block(id: ${oldId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEdgelessTextConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:edgeless-text';
|
||||
|
||||
override async createBlock(
|
||||
edgelessText: BlockSnapshot
|
||||
): Promise<string | null> {
|
||||
const oldId = edgelessText.id;
|
||||
delete edgelessText.props.index;
|
||||
if (!edgelessText.props.xywh) {
|
||||
console.error(
|
||||
`EdgelessText block(id: ${oldId}) does not have xywh property`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (!this.surface) {
|
||||
return null;
|
||||
}
|
||||
const newId = await this.onBlockSnapshotPaste(
|
||||
edgelessText,
|
||||
this.std.store,
|
||||
this.surface.model.id
|
||||
);
|
||||
if (!newId) {
|
||||
console.error(`Failed to paste EdgelessText block(id: ${oldId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardImageConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:image';
|
||||
|
||||
override async createBlock(image: BlockSnapshot) {
|
||||
const { xywh, rotate, sourceId, size, width, height, caption } =
|
||||
image.props;
|
||||
|
||||
if (!this.surface) return null;
|
||||
|
||||
if (!(await this.std.workspace.blobSync.get(sourceId as string))) {
|
||||
return null;
|
||||
}
|
||||
return this.crud.addBlock(
|
||||
'affine:image',
|
||||
{
|
||||
caption,
|
||||
sourceId,
|
||||
xywh,
|
||||
rotate,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardFrameConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:frame';
|
||||
|
||||
override createBlock(
|
||||
frame: BlockSnapshot,
|
||||
context: ClipboardConfigCreationContext
|
||||
): string | null {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const { oldToNewIdMap, newPresentationIndexes } = context;
|
||||
const { xywh, title, background, childElementIds } = frame.props;
|
||||
|
||||
const newChildElementIds: Record<string, boolean> = {};
|
||||
|
||||
if (typeof childElementIds === 'object' && childElementIds !== null) {
|
||||
Object.keys(childElementIds).forEach(oldId => {
|
||||
const newId = oldToNewIdMap.get(oldId);
|
||||
if (newId) {
|
||||
newChildElementIds[newId] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const frameId = this.crud.addBlock(
|
||||
'affine:frame',
|
||||
{
|
||||
xywh,
|
||||
background,
|
||||
title: fromJSON(title),
|
||||
childElementIds: newChildElementIds,
|
||||
presentationIndex: newPresentationIndexes.get(frame.id),
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return frameId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardAttachmentConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:attachment';
|
||||
|
||||
override async createBlock(
|
||||
attachment: BlockSnapshot
|
||||
): Promise<string | null> {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const { xywh, rotate, sourceId, name, size, type, embed, style } =
|
||||
attachment.props;
|
||||
|
||||
if (!(await this.std.workspace.blobSync.get(sourceId as string))) {
|
||||
return null;
|
||||
}
|
||||
const attachmentId = this.crud.addBlock(
|
||||
'affine:attachment',
|
||||
{
|
||||
xywh,
|
||||
rotate,
|
||||
sourceId,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
embed,
|
||||
style,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return attachmentId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardBookmarkConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:bookmark';
|
||||
|
||||
override createBlock(bookmark: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const { xywh, style, url, caption, description, icon, image, title } =
|
||||
bookmark.props;
|
||||
|
||||
const bookmarkId = this.crud.addBlock(
|
||||
'affine:bookmark',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
description,
|
||||
icon,
|
||||
image,
|
||||
title,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return bookmarkId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedFigmaConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-figma';
|
||||
|
||||
override createBlock(figmaEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
const { xywh, style, url, caption, title, description } = figmaEmbed.props;
|
||||
|
||||
const embedFigmaId = this.crud.addBlock(
|
||||
'affine:embed-figma',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedFigmaId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedGithubConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-github';
|
||||
|
||||
override createBlock(githubEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const {
|
||||
xywh,
|
||||
style,
|
||||
owner,
|
||||
repo,
|
||||
githubType,
|
||||
githubId,
|
||||
url,
|
||||
caption,
|
||||
image,
|
||||
status,
|
||||
statusReason,
|
||||
title,
|
||||
description,
|
||||
createdAt,
|
||||
assignees,
|
||||
} = githubEmbed.props;
|
||||
|
||||
const embedGithubId = this.crud.addBlock(
|
||||
'affine:embed-github',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
owner,
|
||||
repo,
|
||||
githubType,
|
||||
githubId,
|
||||
url,
|
||||
caption,
|
||||
image,
|
||||
status,
|
||||
statusReason,
|
||||
title,
|
||||
description,
|
||||
createdAt,
|
||||
assignees,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedGithubId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedHtmlConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-html';
|
||||
|
||||
override createBlock(htmlEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
const { xywh, style, caption, html, design } = htmlEmbed.props;
|
||||
|
||||
const embedHtmlId = this.crud.addBlock(
|
||||
'affine:embed-html',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
html,
|
||||
design,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedHtmlId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedLoomConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-loom';
|
||||
|
||||
override createBlock(loomEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
const { xywh, style, url, caption, videoId, image, title, description } =
|
||||
loomEmbed.props;
|
||||
|
||||
const embedLoomId = this.crud.addBlock(
|
||||
'affine:embed-loom',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedLoomId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedYoutubeConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-youtube';
|
||||
|
||||
override createBlock(youtubeEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
const {
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
creator,
|
||||
creatorUrl,
|
||||
creatorImage,
|
||||
} = youtubeEmbed.props;
|
||||
|
||||
const embedYoutubeId = this.crud.addBlock(
|
||||
'affine:embed-youtube',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
url,
|
||||
caption,
|
||||
videoId,
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
creator,
|
||||
creatorUrl,
|
||||
creatorImage,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return embedYoutubeId;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedIframeConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-iframe';
|
||||
|
||||
override createBlock(embedIframe: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
const {
|
||||
xywh,
|
||||
caption,
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
iframeUrl,
|
||||
scale,
|
||||
width,
|
||||
height,
|
||||
} = embedIframe.props;
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-iframe',
|
||||
{
|
||||
url,
|
||||
iframeUrl,
|
||||
xywh,
|
||||
caption,
|
||||
title,
|
||||
description,
|
||||
scale,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedLinkedDocConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-linked-doc';
|
||||
|
||||
override createBlock(linkedDocEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const { xywh, style, caption, pageId, params, title, description } =
|
||||
linkedDocEmbed.props;
|
||||
const referenceInfo = ReferenceInfoSchema.parse({
|
||||
pageId,
|
||||
params,
|
||||
title,
|
||||
description,
|
||||
});
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
...referenceInfo,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessClipboardEmbedSyncedDocConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = 'affine:embed-synced-doc';
|
||||
|
||||
override createBlock(syncedDocEmbed: BlockSnapshot): string | null {
|
||||
if (!this.surface) return null;
|
||||
|
||||
const { xywh, style, caption, scale, pageId, params } =
|
||||
syncedDocEmbed.props;
|
||||
const referenceInfo = ReferenceInfoSchema.parse({ pageId, params });
|
||||
|
||||
return this.crud.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
xywh,
|
||||
style,
|
||||
caption,
|
||||
scale,
|
||||
...referenceInfo,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,22 @@ import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
import { CommonSpecs } from '../common-specs/index.js';
|
||||
import { edgelessNavigatorBgWidget } from '../widgets/edgeless-navigator-bg/index.js';
|
||||
import { AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET } from '../widgets/edgeless-zoom-toolbar/index.js';
|
||||
import {
|
||||
EdgelessClipboardAttachmentConfig,
|
||||
EdgelessClipboardBookmarkConfig,
|
||||
EdgelessClipboardEdgelessTextConfig,
|
||||
EdgelessClipboardEmbedFigmaConfig,
|
||||
EdgelessClipboardEmbedGithubConfig,
|
||||
EdgelessClipboardEmbedHtmlConfig,
|
||||
EdgelessClipboardEmbedIframeConfig,
|
||||
EdgelessClipboardEmbedLinkedDocConfig,
|
||||
EdgelessClipboardEmbedLoomConfig,
|
||||
EdgelessClipboardEmbedSyncedDocConfig,
|
||||
EdgelessClipboardEmbedYoutubeConfig,
|
||||
EdgelessClipboardFrameConfig,
|
||||
EdgelessClipboardImageConfig,
|
||||
EdgelessClipboardNoteConfig,
|
||||
} from './clipboard/config.js';
|
||||
import { NOTE_SLICER_WIDGET } from './components/note-slicer/index.js';
|
||||
import { EDGELESS_DRAGGING_AREA_WIDGET } from './components/rects/edgeless-dragging-area-rect.js';
|
||||
import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selected-rect.js';
|
||||
@@ -56,6 +72,23 @@ class EdgelessLocker extends LifeCycleWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
const EdgelessClipboardConfigs: ExtensionType[] = [
|
||||
EdgelessClipboardNoteConfig,
|
||||
EdgelessClipboardEdgelessTextConfig,
|
||||
EdgelessClipboardImageConfig,
|
||||
EdgelessClipboardFrameConfig,
|
||||
EdgelessClipboardAttachmentConfig,
|
||||
EdgelessClipboardBookmarkConfig,
|
||||
EdgelessClipboardEmbedFigmaConfig,
|
||||
EdgelessClipboardEmbedGithubConfig,
|
||||
EdgelessClipboardEmbedHtmlConfig,
|
||||
EdgelessClipboardEmbedLoomConfig,
|
||||
EdgelessClipboardEmbedYoutubeConfig,
|
||||
EdgelessClipboardEmbedIframeConfig,
|
||||
EdgelessClipboardEmbedLinkedDocConfig,
|
||||
EdgelessClipboardEmbedSyncedDocConfig,
|
||||
];
|
||||
|
||||
const EdgelessCommonExtension: ExtensionType[] = [
|
||||
CommonSpecs,
|
||||
ToolController,
|
||||
@@ -65,6 +98,7 @@ const EdgelessCommonExtension: ExtensionType[] = [
|
||||
ConnectorElementView,
|
||||
...quickTools,
|
||||
...seniorTools,
|
||||
...EdgelessClipboardConfigs,
|
||||
].flat();
|
||||
|
||||
export const EdgelessRootBlockSpec: ExtensionType[] = [
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { BlockSuiteError } from '@blocksuite/global/exceptions';
|
||||
import { type BlockSnapshot, Extension, type Store } from '@blocksuite/store';
|
||||
|
||||
import { getSurfaceComponent } from '../utils/get-surface-block';
|
||||
import { EdgelessCRUDIdentifier } from './crud-extension';
|
||||
|
||||
export type ClipboardConfigCreationContext = {
|
||||
/**
|
||||
* element old id to new id
|
||||
*/
|
||||
oldToNewIdMap: Map<string, string>;
|
||||
/**
|
||||
* element old id to new layer index
|
||||
*/
|
||||
originalIndexes: Map<string, string>;
|
||||
|
||||
/**
|
||||
* frame old id to new presentation index
|
||||
*/
|
||||
newPresentationIndexes: Map<string, string>;
|
||||
};
|
||||
|
||||
export const EdgelessClipboardConfigIdentifier =
|
||||
createIdentifier<EdgelessClipboardConfig>('edgeless-clipboard-config');
|
||||
|
||||
export abstract class EdgelessClipboardConfig extends Extension {
|
||||
static key: string;
|
||||
|
||||
constructor(readonly std: BlockStdScope) {
|
||||
super();
|
||||
}
|
||||
|
||||
get surface() {
|
||||
return getSurfaceComponent(this.std);
|
||||
}
|
||||
|
||||
get crud() {
|
||||
return this.std.get(EdgelessCRUDIdentifier);
|
||||
}
|
||||
|
||||
onBlockSnapshotPaste = async (
|
||||
snapshot: BlockSnapshot,
|
||||
doc: Store,
|
||||
parent?: string,
|
||||
index?: number
|
||||
) => {
|
||||
const block = await this.std.clipboard.pasteBlockSnapshot(
|
||||
snapshot,
|
||||
doc,
|
||||
parent,
|
||||
index
|
||||
);
|
||||
return block?.id ?? null;
|
||||
};
|
||||
|
||||
abstract createBlock(
|
||||
snapshot: BlockSnapshot,
|
||||
context: ClipboardConfigCreationContext
|
||||
): string | null | Promise<string | null>;
|
||||
|
||||
static override setup(di: Container) {
|
||||
if (!this.key) {
|
||||
throw new BlockSuiteError(
|
||||
BlockSuiteError.ErrorCode.ValueNotExists,
|
||||
'Key is not defined in the EdgelessClipboardConfig'
|
||||
);
|
||||
}
|
||||
|
||||
di.add(
|
||||
this as unknown as { new (std: BlockStdScope): EdgelessClipboardConfig },
|
||||
[StdIdentifier]
|
||||
);
|
||||
|
||||
di.addImpl(EdgelessClipboardConfigIdentifier(this.key), provider =>
|
||||
provider.get(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './clipboard-config';
|
||||
export * from './crud-extension';
|
||||
export * from './export-manager';
|
||||
export * from './legacy-slot-extension';
|
||||
|
||||
@@ -2,7 +2,7 @@ import rehypeParse from 'rehype-parse';
|
||||
import { unified } from 'unified';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { onlyContainImgElement } from '../clipboard/index.js';
|
||||
import { onlyContainImgElement } from '../clipboard/utils.js';
|
||||
|
||||
describe('only contains img elements', () => {
|
||||
test('normal with head', () => {
|
||||
|
||||
@@ -9,12 +9,12 @@ import type {
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
import DOMPurify from 'dompurify';
|
||||
import type { RootContentMap } from 'hast';
|
||||
import * as lz from 'lz-string';
|
||||
import rehypeParse from 'rehype-parse';
|
||||
import { unified } from 'unified';
|
||||
|
||||
import { LifeCycleWatcher } from '../extension/index.js';
|
||||
import { onlyContainImgElement } from './utils.js';
|
||||
|
||||
type AdapterConstructor<T extends BaseAdapter> =
|
||||
| { new (job: Transformer): T }
|
||||
@@ -28,38 +28,6 @@ type AdapterMap = Map<
|
||||
}
|
||||
>;
|
||||
|
||||
type HastUnionType<
|
||||
K extends keyof RootContentMap,
|
||||
V extends RootContentMap[K],
|
||||
> = V;
|
||||
|
||||
export function onlyContainImgElement(
|
||||
ast: HastUnionType<keyof RootContentMap, RootContentMap[keyof RootContentMap]>
|
||||
): 'yes' | 'no' | 'maybe' {
|
||||
if (ast.type === 'element') {
|
||||
switch (ast.tagName) {
|
||||
case 'html':
|
||||
case 'body':
|
||||
return ast.children.map(onlyContainImgElement).reduce((a, b) => {
|
||||
if (a === 'no' || b === 'no') {
|
||||
return 'no';
|
||||
}
|
||||
if (a === 'maybe' && b === 'maybe') {
|
||||
return 'maybe';
|
||||
}
|
||||
return 'yes';
|
||||
}, 'maybe');
|
||||
case 'img':
|
||||
return 'yes';
|
||||
case 'head':
|
||||
return 'maybe';
|
||||
default:
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
return 'maybe';
|
||||
}
|
||||
|
||||
export class Clipboard extends LifeCycleWatcher {
|
||||
static override readonly key = 'clipboard';
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { RootContentMap } from 'hast';
|
||||
|
||||
type HastUnionType<
|
||||
K extends keyof RootContentMap,
|
||||
V extends RootContentMap[K],
|
||||
> = V;
|
||||
|
||||
export function onlyContainImgElement(
|
||||
ast: HastUnionType<keyof RootContentMap, RootContentMap[keyof RootContentMap]>
|
||||
): 'yes' | 'no' | 'maybe' {
|
||||
if (ast.type === 'element') {
|
||||
switch (ast.tagName) {
|
||||
case 'html':
|
||||
case 'body':
|
||||
return ast.children.map(onlyContainImgElement).reduce((a, b) => {
|
||||
if (a === 'no' || b === 'no') {
|
||||
return 'no';
|
||||
}
|
||||
if (a === 'maybe' && b === 'maybe') {
|
||||
return 'maybe';
|
||||
}
|
||||
return 'yes';
|
||||
}, 'maybe');
|
||||
case 'img':
|
||||
return 'yes';
|
||||
case 'head':
|
||||
return 'maybe';
|
||||
default:
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
return 'maybe';
|
||||
}
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
import { patchDatabaseBlockConfigService } from '../extensions/database-block-config-service';
|
||||
import { patchDocModeService } from '../extensions/doc-mode-service';
|
||||
import { patchDocUrlExtensions } from '../extensions/doc-url';
|
||||
import { EdgelessClipboardWatcher } from '../extensions/edgeless-clipboard';
|
||||
import { EdgelessClipboardAIChatConfig } from '../extensions/edgeless-clipboard';
|
||||
import { patchForClipboardInElectron } from '../extensions/electron-clipboard';
|
||||
import { enableEditorExtension } from '../extensions/entry/enable-editor';
|
||||
import { enableMobileExtension } from '../extensions/entry/enable-mobile';
|
||||
@@ -169,7 +169,7 @@ const usePatchSpecs = (mode: DocMode) => {
|
||||
patchNotificationService(confirmModal),
|
||||
patchPeekViewService(peekViewService),
|
||||
patchOpenDocExtension(),
|
||||
EdgelessClipboardWatcher,
|
||||
EdgelessClipboardAIChatConfig,
|
||||
patchDocUrlExtensions(framework),
|
||||
patchQuickSearchService(framework),
|
||||
patchSideBarService(framework),
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
import { AIChatBlockSchema } from '@affine/core/blocksuite/ai/blocks';
|
||||
import { LifeCycleWatcher } from '@blocksuite/affine/block-std';
|
||||
import { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
|
||||
import { EdgelessClipboardConfig } from '@blocksuite/affine/blocks/surface';
|
||||
import type { BlockSnapshot } from '@blocksuite/affine/store';
|
||||
|
||||
export class EdgelessClipboardWatcher extends LifeCycleWatcher {
|
||||
static override key = 'edgeless-clipboard-watcher';
|
||||
export class EdgelessClipboardAIChatConfig extends EdgelessClipboardConfig {
|
||||
static override readonly key = AIChatBlockSchema.model.flavour;
|
||||
|
||||
override mounted() {
|
||||
super.mounted();
|
||||
const { view } = this.std;
|
||||
view.viewUpdated.subscribe(payload => {
|
||||
if (payload.type !== 'block' || payload.method !== 'add') {
|
||||
return;
|
||||
}
|
||||
const component = payload.view;
|
||||
if (!(component instanceof EdgelessRootBlockComponent)) {
|
||||
return;
|
||||
}
|
||||
const AIChatBlockFlavour = AIChatBlockSchema.model.flavour;
|
||||
const createFunc = (block: BlockSnapshot) => {
|
||||
const { xywh, scale, messages, sessionId, rootDocId, rootWorkspaceId } =
|
||||
block.props;
|
||||
const blockId = component.service.crud.addBlock(
|
||||
AIChatBlockFlavour,
|
||||
{
|
||||
xywh,
|
||||
scale,
|
||||
messages,
|
||||
sessionId,
|
||||
rootDocId,
|
||||
rootWorkspaceId,
|
||||
},
|
||||
component.surface.model.id
|
||||
);
|
||||
return blockId;
|
||||
};
|
||||
component.clipboardController.registerBlock(
|
||||
AIChatBlockFlavour,
|
||||
createFunc
|
||||
);
|
||||
});
|
||||
override createBlock(block: BlockSnapshot): null | string {
|
||||
if (!this.surface) return null;
|
||||
const { xywh, scale, messages, sessionId, rootDocId, rootWorkspaceId } =
|
||||
block.props;
|
||||
const blockId = this.crud.addBlock(
|
||||
AIChatBlockSchema.model.flavour,
|
||||
{
|
||||
xywh,
|
||||
scale,
|
||||
messages,
|
||||
sessionId,
|
||||
rootDocId,
|
||||
rootWorkspaceId,
|
||||
},
|
||||
this.surface.model.id
|
||||
);
|
||||
return blockId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user