fix(editor): paste surface-ref block to another doc as embed-linked-doc block (#10274)

[BS-2155](https://linear.app/affine-design/issue/BS-2155/复制-insert-frame-group-粘贴后,应当变为-block-ref-link)
This commit is contained in:
donteatfriedrice
2025-02-19 07:02:26 +00:00
parent 751f229e30
commit 319d909ac8
6 changed files with 98 additions and 15 deletions
@@ -1,3 +1,4 @@
export * from './code';
export * from './copy';
export * from './paste';
export * from './surface-ref-to-embed';
@@ -17,12 +17,16 @@ export const surfaceRefToEmbed =
payload.snapshot.flavour === 'affine:surface-ref' &&
!std.store.hasBlock(payload.snapshot.id)
) {
const id = payload.snapshot.id;
// The blockId of the original surface-ref block
const blockId = payload.snapshot.id;
payload.snapshot.id = std.workspace.idGenerator();
payload.snapshot.flavour = 'affine:embed-linked-doc';
payload.snapshot.props = {
blockId: id,
pageId,
params: {
mode: 'page',
blockIds: [blockId],
},
};
}
});
@@ -12,6 +12,7 @@ import {
NoteBlockModel,
RootBlockModel,
} from '@blocksuite/affine-model';
import { surfaceRefToEmbed } from '@blocksuite/affine-shared/adapters';
import {
BLOCK_CHILDREN_CONTAINER_PADDING_LEFT,
EMBED_CARD_HEIGHT,
@@ -68,7 +69,6 @@ import { PreviewHelper } from '../helpers/preview-helper.js';
import { gfxBlocksFilter } from '../middleware/blocks-filter.js';
import { newIdCrossDoc } from '../middleware/new-id-cross-doc.js';
import { reorderList } from '../middleware/reorder-list';
import { surfaceRefToEmbed } from '../middleware/surface-ref-to-embed.js';
import {
containBlock,
extractIdsFromSnapshot,
@@ -1,4 +1,8 @@
import { deleteTextCommand } from '@blocksuite/affine-components/rich-text';
import {
pasteMiddleware,
surfaceRefToEmbed,
} from '@blocksuite/affine-shared/adapters';
import {
clearAndSelectFirstModelCommand,
deleteSelectedModelsCommand,
@@ -13,6 +17,7 @@ import type { UIEventHandler } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { BlockSnapshot, Store } from '@blocksuite/store';
import { replaceIdMiddleware } from '../../_common/transformers/middlewares';
import { ReadOnlyClipboard } from './readonly-clipboard';
/**
@@ -22,6 +27,23 @@ import { ReadOnlyClipboard } from './readonly-clipboard';
export class PageClipboard extends ReadOnlyClipboard {
protected _init = () => {
this._initAdapters();
const paste = pasteMiddleware(this._std);
// Use surfaceRefToEmbed middleware to convert surface-ref to embed-linked-doc
// When pastina a surface-ref block to another doc
const surfaceRefToEmbedMiddleware = surfaceRefToEmbed(this._std);
const replaceId = replaceIdMiddleware(
this._std.store.workspace.idGenerator
);
this._std.clipboard.use(paste);
this._std.clipboard.use(surfaceRefToEmbedMiddleware);
this._std.clipboard.use(replaceId);
this._disposables.add({
dispose: () => {
this._std.clipboard.unuse(paste);
this._std.clipboard.unuse(surfaceRefToEmbedMiddleware);
this._std.clipboard.unuse(replaceId);
},
});
};
onBlockSnapshotPaste = async (
@@ -143,3 +165,5 @@ export class PageClipboard extends ReadOnlyClipboard {
}
}
}
export { pasteMiddleware };
@@ -5,7 +5,6 @@ import {
ImageAdapter,
MixTextAdapter,
NotionTextAdapter,
pasteMiddleware,
} from '@blocksuite/affine-shared/adapters';
import {
copySelectedModelsCommand,
@@ -17,7 +16,6 @@ import { DisposableGroup } from '@blocksuite/global/utils';
import {
defaultImageProxyMiddleware,
replaceIdMiddleware,
titleMiddleware,
} from '../../_common/transformers/middlewares.js';
import { ClipboardAdapter } from './adapter.js';
@@ -64,12 +62,7 @@ export class ReadOnlyClipboard {
this._std.clipboard.registerAdapter('text/plain', MixTextAdapter, 70);
this._std.clipboard.registerAdapter('*/*', AttachmentAdapter, 60);
const copy = copyMiddleware(this._std);
const paste = pasteMiddleware(this._std);
this._std.clipboard.use(copy);
this._std.clipboard.use(paste);
this._std.clipboard.use(
replaceIdMiddleware(this._std.store.workspace.idGenerator)
);
this._std.clipboard.use(
titleMiddleware(this._std.store.workspace.meta.docMetas)
);
@@ -91,10 +84,6 @@ export class ReadOnlyClipboard {
this._std.clipboard.unregisterAdapter('text/html');
this._std.clipboard.unregisterAdapter('*/*');
this._std.clipboard.unuse(copy);
this._std.clipboard.unuse(paste);
this._std.clipboard.unuse(
replaceIdMiddleware(this._std.store.workspace.idGenerator)
);
this._std.clipboard.unuse(
titleMiddleware(this._std.store.workspace.meta.docMetas)
);
@@ -135,4 +124,4 @@ export class ReadOnlyClipboard {
}
}
export { copyMiddleware, pasteMiddleware };
export { copyMiddleware };
@@ -1,5 +1,10 @@
import { test } from '@affine-test/kit/playwright';
import { pasteContent } from '@affine-test/kit/utils/clipboard';
import {
clickEdgelessModeButton,
clickPageModeButton,
locateEditorContainer,
} from '@affine-test/kit/utils/editor';
import {
copyByKeyboard,
pasteByKeyboard,
@@ -8,6 +13,7 @@ import {
import { openHomePage } from '@affine-test/kit/utils/load-page';
import {
clickNewPageButton,
getBlockSuiteEditorTitle,
type,
waitForEditorLoad,
} from '@affine-test/kit/utils/page-logic';
@@ -141,3 +147,62 @@ test.describe('paste in multiple blocks text selection', () => {
await verifyParagraphContent(page, 3, 'test world');
});
});
test('paste surface-ref block to another doc as embed-linked-doc block', async ({
page,
}) => {
await openHomePage(page);
await clickNewPageButton(page, 'Clipboard Test');
await waitForEditorLoad(page);
await clickEdgelessModeButton(page);
const container = locateEditorContainer(page);
await container.click();
// add a shape
await page.keyboard.press('s');
// click to add a shape
await container.click({ position: { x: 100, y: 500 } });
await page.waitForTimeout(50);
// add a frame
await page.keyboard.press('f');
await page.waitForTimeout(50);
// click on the frame title to trigger the change frame button toolbar
const frameTitle = page.locator('affine-frame-title');
await frameTitle.click();
await page.waitForTimeout(50);
const changeFrameButton = page.locator('edgeless-change-frame-button');
// get insert into page button which with aria-label 'Insert into Page'
const insertIntoPageButton = changeFrameButton.locator(
`editor-icon-button[aria-label="Insert into Page"]`
);
await insertIntoPageButton.click();
await clickPageModeButton(page);
await page.waitForTimeout(50);
// copy surface-ref block
const surfaceRefBlock = page.locator('.affine-surface-ref');
await surfaceRefBlock.click();
await page.waitForTimeout(50);
await copyByKeyboard(page);
// paste to another doc
await clickNewPageButton(page);
await waitForEditorLoad(page);
const title2 = getBlockSuiteEditorTitle(page);
await title2.pressSequentially('page2');
await page.keyboard.press('Enter');
await page.waitForTimeout(50);
// paste the surface-ref block
await pasteByKeyboard(page);
await page.waitForTimeout(50);
const embedLinkedDocBlock = page.locator('affine-embed-linked-doc-block');
await expect(embedLinkedDocBlock).toBeVisible();
const embedLinkedDocBlockTitle = embedLinkedDocBlock.locator(
'.affine-embed-linked-doc-content-title-text'
);
await expect(embedLinkedDocBlockTitle).toHaveText('Clipboard Test');
});