mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user