fix(editor): support copying single image from edgeless and pasting to page (#12709)

Closes: [BS-3586](https://linear.app/affine-design/issue/BS-3586/复制白板图片,然后粘贴到-page,图片失败)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Copying a single selected image in edgeless mode now places the image directly onto the system clipboard as a native image blob for smoother pasting.

- **Bug Fixes**
  - Enhanced clipboard handling to better manage image and text data inclusion, with improved fallback for snapshot HTML.

- **Tests**
  - Added an end-to-end test verifying image copy-paste functionality between edgeless and page editor modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
fundon
2025-06-06 01:17:58 +00:00
parent 512a908fd4
commit 7d1f2adb7f
7 changed files with 176 additions and 74 deletions
@@ -35,6 +35,7 @@ import {
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import {
convertToPng,
isInsidePageEditor,
isTopLevelBlock,
isUrlInClipboard,
@@ -67,7 +68,7 @@ import * as Y from 'yjs';
import { PageClipboard } from '../../clipboard/index.js';
import { getSortedCloneElements } from '../utils/clone-utils.js';
import { isCanvasElementWithText } from '../utils/query.js';
import { isCanvasElementWithText, isImageBlock } from '../utils/query.js';
import { createElementsFromClipboardDataCommand } from './command.js';
import {
isPureFileInClipboard,
@@ -126,6 +127,49 @@ export class EdgelessClipboardController extends PageClipboard {
return;
}
// Only when an image is selected, it can be pasted normally to page mode.
if (elements.length === 1 && isImageBlock(elements[0])) {
const element = elements[0];
const sourceId = element.props.sourceId$.peek();
if (!sourceId) return;
await this.std.clipboard.writeToClipboard(async items => {
const job = this.std.store.getTransformer();
await job.assetsManager.readFromBlob(sourceId);
let blob = job.assetsManager.getAssets().get(sourceId) ?? null;
if (!blob) {
return items;
}
let type = blob.type;
let supported = false;
try {
supported = ClipboardItem?.supports(type) ?? false;
} catch (err) {
console.error(err);
}
// TODO(@fundon): when converting jpeg to png, image may become larger and exceed the limit.
if (!supported) {
type = 'image/png';
blob = await convertToPng(blob);
}
if (blob) {
return {
...items,
[`${type}`]: blob,
};
}
return items;
});
return;
}
await this.std.clipboard.writeToClipboard(async _items => {
const data = await prepareClipboardData(elements, this.std);
return {