fix(editor): image size and xywh when converting attachment to image (#10200)

In Edgeless, the image size should be read when converting attachment to image:

* fix `size`
* fix `xywh`
This commit is contained in:
fundon
2025-02-20 05:16:21 +00:00
parent ff0ce1a962
commit adc003862b
10 changed files with 87 additions and 41 deletions
+46 -11
View File
@@ -1,20 +1,25 @@
import type {
AttachmentBlockModel,
ImageBlockProps,
import {
type AttachmentBlockModel,
type ImageBlockProps,
MAX_IMAGE_WIDTH,
} from '@blocksuite/affine-model';
import { FileSizeLimitService } from '@blocksuite/affine-shared/services';
import {
readImageSize,
transformModel,
withTempBlobData,
} from '@blocksuite/affine-shared/utils';
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
import type { Container } from '@blocksuite/global/di';
import { createIdentifier } from '@blocksuite/global/di';
import { Bound } from '@blocksuite/global/utils';
import type { ExtensionType } from '@blocksuite/store';
import { Extension } from '@blocksuite/store';
import type { TemplateResult } from 'lit';
import { html } from 'lit';
import { getAttachmentBlob } from './utils';
export type AttachmentEmbedConfig = {
name: string;
/**
@@ -24,7 +29,10 @@ export type AttachmentEmbedConfig = {
/**
* The action will be executed when the 「Turn into embed view」 button is clicked.
*/
action?: (model: AttachmentBlockModel) => Promise<void> | void;
action?: (
model: AttachmentBlockModel,
std: BlockStdScope
) => Promise<void> | void;
/**
* The template will be used to render the embed view.
*/
@@ -89,11 +97,11 @@ export class AttachmentEmbedService extends Extension {
// Converts to embed view.
convertTo(model: AttachmentBlockModel, maxFileSize = this._maxFileSize) {
const config = this.values.find(config => config.check(model, maxFileSize));
if (!config || !config.action) {
if (!config?.action) {
model.doc.updateBlock(model, { embed: true });
return;
}
config.action(model)?.catch(console.error);
config.action(model, this.std)?.catch(console.error);
}
embedded(model: AttachmentBlockModel, maxFileSize = this._maxFileSize) {
@@ -123,7 +131,12 @@ const embedConfig: AttachmentEmbedConfig[] = [
check: model =>
model.doc.schema.flavourSchemaMap.has('affine:image') &&
model.type.startsWith('image/'),
action: model => turnIntoImageBlock(model),
async action(model, std) {
const component = std.view.getBlock(model.id);
if (!component) return;
await turnIntoImageBlock(model);
},
},
{
name: 'pdf',
@@ -171,7 +184,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
/**
* Turn the attachment block into an image block.
*/
export function turnIntoImageBlock(model: AttachmentBlockModel) {
export async function turnIntoImageBlock(model: AttachmentBlockModel) {
if (!model.doc.schema.flavourSchemaMap.has('affine:image')) {
console.error('The image flavour is not supported!');
return;
@@ -183,15 +196,37 @@ export function turnIntoImageBlock(model: AttachmentBlockModel) {
const { saveAttachmentData, getImageData } = withTempBlobData();
saveAttachmentData(sourceId, { name: model.name });
const imageConvertData = model.sourceId
? getImageData(model.sourceId)
let imageSize = model.sourceId ? getImageData(model.sourceId) : undefined;
const bounds = model.xywh
? Bound.fromXYWH(model.deserializedXYWH)
: undefined;
if (bounds) {
if (!imageSize?.width || !imageSize?.height) {
const blob = await getAttachmentBlob(model);
if (blob) {
imageSize = await readImageSize(blob);
}
}
if (imageSize?.width && imageSize?.height) {
const p = imageSize.height / imageSize.width;
imageSize.width = Math.min(imageSize.width, MAX_IMAGE_WIDTH);
imageSize.height = imageSize.width * p;
bounds.w = imageSize.width;
bounds.h = imageSize.height;
}
}
const others = bounds ? { xywh: bounds.serialize() } : undefined;
const imageProp: Partial<ImageBlockProps> = {
sourceId,
caption: model.caption,
size: model.size,
...imageConvertData,
...imageSize,
...others,
};
transformModel(model, 'affine:image', imageProp);
}
@@ -97,7 +97,7 @@ export async function uploadAttachmentBlob(
}
}
async function getAttachmentBlob(model: AttachmentBlockModel) {
export async function getAttachmentBlob(model: AttachmentBlockModel) {
const sourceId = model.sourceId;
if (!sourceId) {
return null;
+1 -21
View File
@@ -12,6 +12,7 @@ import {
import {
downloadBlob,
humanFileSize,
readImageSize,
transformModel,
withTempBlobData,
} from '@blocksuite/affine-shared/utils';
@@ -425,27 +426,6 @@ export async function turnImageIntoCardView(
transformModel(model, 'affine:attachment', attachmentProp);
}
export function readImageSize(file: File | Blob) {
return new Promise<{ width: number; height: number }>(resolve => {
const size = { width: 0, height: 0 };
const img = new Image();
img.onload = () => {
size.width = img.width;
size.height = img.height;
URL.revokeObjectURL(img.src);
resolve(size);
};
img.onerror = () => {
URL.revokeObjectURL(img.src);
resolve(size);
};
img.src = URL.createObjectURL(file);
});
}
export async function addImages(
std: BlockStdScope,
files: File[],
+1
View File
@@ -25,6 +25,7 @@
"@toeverything/theme": "^1.1.11",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
"dompurify": "^3.2.4",
"fractional-indexing": "^3.2.0",
"lit": "^3.2.0",
"lodash.clonedeep": "^4.5.0",
@@ -0,0 +1,28 @@
import DOMPurify from 'dompurify';
export function readImageSize(file: File | Blob) {
return new Promise<{ width: number; height: number }>(resolve => {
const size = { width: 0, height: 0 };
if (!file.type.startsWith('image/')) {
resolve(size);
return;
}
const img = new Image();
img.onload = () => {
size.width = img.width;
size.height = img.height;
URL.revokeObjectURL(img.src);
resolve(size);
};
img.onerror = () => {
URL.revokeObjectURL(img.src);
resolve(size);
};
const sanitizedURL = DOMPurify.sanitize(URL.createObjectURL(file));
img.src = sanitizedURL;
});
}
@@ -8,6 +8,7 @@ export * from './edgeless';
export * from './event';
export * from './file';
export * from './fractional-indexing';
export * from './image';
export * from './insert';
export * from './is-abort-error';
export * from './math';
+1 -1
View File
@@ -51,7 +51,7 @@
"@toeverything/theme": "^1.1.11",
"@vanilla-extract/css": "^1.17.0",
"date-fns": "^4.0.0",
"dompurify": "^3.1.6",
"dompurify": "^3.2.4",
"fflate": "^0.8.2",
"file-type": "^20.0.0",
"fractional-indexing": "^3.2.0",
+1 -1
View File
@@ -23,7 +23,7 @@
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@types/hast": "^3.0.4",
"dompurify": "^3.1.6",
"dompurify": "^3.2.4",
"fractional-indexing": "^3.2.0",
"lib0": "^0.2.97",
"lit": "^3.2.0",
@@ -42,10 +42,10 @@
"version": 1,
"props": {
"sourceId": "ejImogf-Tb7AuKY-v94uz1zuOJbClqK-tWBxVr_ksGA=",
"width": 0,
"height": 0,
"width": 460,
"height": 345,
"index": "a0",
"xywh": "[0,0,0,0]",
"xywh": "[0,0,460,345]",
"lockedBySelf": false,
"rotate": 0,
"size": 45801,
+4 -3
View File
@@ -3749,6 +3749,7 @@ __metadata:
"@types/lodash.clonedeep": "npm:^4.5.9"
"@types/lodash.mergewith": "npm:^4"
"@types/mdast": "npm:^4.0.4"
dompurify: "npm:^3.2.4"
fractional-indexing: "npm:^3.2.0"
lit: "npm:^3.2.0"
lodash.clonedeep: "npm:^4.5.0"
@@ -3896,7 +3897,7 @@ __metadata:
"@lit/context": "npm:^1.1.2"
"@preact/signals-core": "npm:^1.8.0"
"@types/hast": "npm:^3.0.4"
dompurify: "npm:^3.1.6"
dompurify: "npm:^3.2.4"
fractional-indexing: "npm:^3.2.0"
lib0: "npm:^0.2.97"
lit: "npm:^3.2.0"
@@ -3954,7 +3955,7 @@ __metadata:
"@vanilla-extract/css": "npm:^1.17.0"
"@vanilla-extract/vite-plugin": "npm:^5.0.0"
date-fns: "npm:^4.0.0"
dompurify: "npm:^3.1.6"
dompurify: "npm:^3.2.4"
fflate: "npm:^0.8.2"
file-type: "npm:^20.0.0"
fractional-indexing: "npm:^3.2.0"
@@ -20043,7 +20044,7 @@ __metadata:
languageName: node
linkType: hard
"dompurify@npm:^3.1.6":
"dompurify@npm:^3.2.4":
version: 3.2.4
resolution: "dompurify@npm:3.2.4"
dependencies: