mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
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:
@@ -107,10 +107,10 @@ export class EmbedHtmlFullscreenToolbar extends LitElement {
|
||||
if (this._copied) return;
|
||||
|
||||
this.embedHtml.std.clipboard
|
||||
.writeToClipboard(items => {
|
||||
items['text/plain'] = this.embedHtml.model.props.html ?? '';
|
||||
return items;
|
||||
})
|
||||
.writeToClipboard(items => ({
|
||||
...items,
|
||||
'text/plain': this.embedHtml.model.props.html ?? '',
|
||||
}))
|
||||
.then(() => {
|
||||
this._copied = true;
|
||||
setTimeout(() => (this._copied = false), 1500);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
NativeClipboardProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
convertToPng,
|
||||
formatSize,
|
||||
getBlockProps,
|
||||
isInsidePageEditor,
|
||||
@@ -111,28 +112,6 @@ export async function resetImageSize(
|
||||
block.store.updateBlock(model, props);
|
||||
}
|
||||
|
||||
function convertToPng(blob: Blob): Promise<Blob | null> {
|
||||
return new Promise(resolve => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', _ => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const c = document.createElement('canvas');
|
||||
c.width = img.width;
|
||||
c.height = img.height;
|
||||
const ctx = c.getContext('2d');
|
||||
if (!ctx) return;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
c.toBlob(resolve, 'image/png');
|
||||
};
|
||||
img.onerror = () => resolve(null);
|
||||
img.src = reader.result as string;
|
||||
});
|
||||
reader.addEventListener('error', () => resolve(null));
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
export async function copyImageBlob(
|
||||
block: ImageBlockComponent | ImageEdgelessBlockComponent
|
||||
) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user