From adc003862bd8cd72196c128bb3877885e91946e7 Mon Sep 17 00:00:00 2001 From: fundon Date: Thu, 20 Feb 2025 05:16:21 +0000 Subject: [PATCH] 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` --- .../affine/block-attachment/src/embed.ts | 57 +++++++++++++++---- .../affine/block-attachment/src/utils.ts | 2 +- blocksuite/affine/block-image/src/utils.ts | 22 +------ blocksuite/affine/shared/package.json | 1 + blocksuite/affine/shared/src/utils/image.ts | 28 +++++++++ blocksuite/affine/shared/src/utils/index.ts | 1 + blocksuite/blocks/package.json | 2 +- blocksuite/framework/block-std/package.json | 2 +- ...ould-turn-attachment-to-image-works-1.json | 6 +- yarn.lock | 7 ++- 10 files changed, 87 insertions(+), 41 deletions(-) create mode 100644 blocksuite/affine/shared/src/utils/image.ts diff --git a/blocksuite/affine/block-attachment/src/embed.ts b/blocksuite/affine/block-attachment/src/embed.ts index 69bb469455..54906e0d9e 100644 --- a/blocksuite/affine/block-attachment/src/embed.ts +++ b/blocksuite/affine/block-attachment/src/embed.ts @@ -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; + action?: ( + model: AttachmentBlockModel, + std: BlockStdScope + ) => Promise | 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 = { sourceId, caption: model.caption, size: model.size, - ...imageConvertData, + ...imageSize, + ...others, }; transformModel(model, 'affine:image', imageProp); } diff --git a/blocksuite/affine/block-attachment/src/utils.ts b/blocksuite/affine/block-attachment/src/utils.ts index 85a7c74fdf..3cf5891d47 100644 --- a/blocksuite/affine/block-attachment/src/utils.ts +++ b/blocksuite/affine/block-attachment/src/utils.ts @@ -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; diff --git a/blocksuite/affine/block-image/src/utils.ts b/blocksuite/affine/block-image/src/utils.ts index 9719cb70bf..3c70f2fa9d 100644 --- a/blocksuite/affine/block-image/src/utils.ts +++ b/blocksuite/affine/block-image/src/utils.ts @@ -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[], diff --git a/blocksuite/affine/shared/package.json b/blocksuite/affine/shared/package.json index 1269d9205c..0572a23c25 100644 --- a/blocksuite/affine/shared/package.json +++ b/blocksuite/affine/shared/package.json @@ -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", diff --git a/blocksuite/affine/shared/src/utils/image.ts b/blocksuite/affine/shared/src/utils/image.ts new file mode 100644 index 0000000000..a781897f47 --- /dev/null +++ b/blocksuite/affine/shared/src/utils/image.ts @@ -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; + }); +} diff --git a/blocksuite/affine/shared/src/utils/index.ts b/blocksuite/affine/shared/src/utils/index.ts index 5cde1bb805..ba6ca7b7cf 100644 --- a/blocksuite/affine/shared/src/utils/index.ts +++ b/blocksuite/affine/shared/src/utils/index.ts @@ -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'; diff --git a/blocksuite/blocks/package.json b/blocksuite/blocks/package.json index 314e537b52..c245f2e634 100644 --- a/blocksuite/blocks/package.json +++ b/blocksuite/blocks/package.json @@ -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", diff --git a/blocksuite/framework/block-std/package.json b/blocksuite/framework/block-std/package.json index 91193e4ca7..f9b82bf003 100644 --- a/blocksuite/framework/block-std/package.json +++ b/blocksuite/framework/block-std/package.json @@ -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", diff --git a/blocksuite/tests-legacy/snapshots/attachment.spec.ts/should-turn-attachment-to-image-works-1.json b/blocksuite/tests-legacy/snapshots/attachment.spec.ts/should-turn-attachment-to-image-works-1.json index 9eb1cb4229..0ee8824968 100644 --- a/blocksuite/tests-legacy/snapshots/attachment.spec.ts/should-turn-attachment-to-image-works-1.json +++ b/blocksuite/tests-legacy/snapshots/attachment.spec.ts/should-turn-attachment-to-image-works-1.json @@ -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, diff --git a/yarn.lock b/yarn.lock index 8d9d48db33..62c12fc0e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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: