fix: Size of inserted retina images (#6350)
* Fix pasted size of retina images * lint * lint
This commit is contained in:
@@ -29,7 +29,7 @@ export type Options = {
|
||||
};
|
||||
};
|
||||
|
||||
const insertFiles = function (
|
||||
const insertFiles = async function (
|
||||
view: EditorView,
|
||||
event:
|
||||
| Event
|
||||
@@ -38,7 +38,7 @@ const insertFiles = function (
|
||||
pos: number,
|
||||
files: File[],
|
||||
options: Options
|
||||
): void {
|
||||
) {
|
||||
const { dictionary, uploadFile, onFileUploadStart, onFileUploadStop } =
|
||||
options;
|
||||
|
||||
@@ -54,14 +54,31 @@ const insertFiles = function (
|
||||
// we'll use this to track of how many files have succeeded or failed
|
||||
let complete = 0;
|
||||
|
||||
const filesToUpload = files.map((file) => ({
|
||||
id: `upload-${uuidv4()}`,
|
||||
isImage:
|
||||
FileHelper.isImage(file) && !options.isAttachment && !!schema.nodes.image,
|
||||
isVideo:
|
||||
FileHelper.isVideo(file) && !options.isAttachment && !!schema.nodes.video,
|
||||
file,
|
||||
}));
|
||||
const filesToUpload = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const isImage =
|
||||
FileHelper.isImage(file) &&
|
||||
!options.isAttachment &&
|
||||
!!schema.nodes.image;
|
||||
const isVideo =
|
||||
FileHelper.isVideo(file) &&
|
||||
!options.isAttachment &&
|
||||
!!schema.nodes.video;
|
||||
const getDimensions = isImage
|
||||
? FileHelper.getImageDimensions
|
||||
: isVideo
|
||||
? FileHelper.getVideoDimensions
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: `upload-${uuidv4()}`,
|
||||
dimensions: await getDimensions?.(file),
|
||||
isImage,
|
||||
isVideo,
|
||||
file,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// the user might have dropped multiple files at once, we need to loop
|
||||
for (const upload of filesToUpload) {
|
||||
@@ -86,11 +103,12 @@ const insertFiles = function (
|
||||
}
|
||||
if (upload.isImage) {
|
||||
const newImg = new Image();
|
||||
newImg.onload = () => {
|
||||
newImg.onload = async () => {
|
||||
const result = findPlaceholder(view.state, upload.id);
|
||||
if (result === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +119,11 @@ const insertFiles = function (
|
||||
.replaceWith(
|
||||
from,
|
||||
to || from,
|
||||
schema.nodes.image.create({ src, ...options.attrs })
|
||||
schema.nodes.image.create({
|
||||
src,
|
||||
...(upload.dimensions ?? {}),
|
||||
...options.attrs,
|
||||
})
|
||||
)
|
||||
.setMeta(uploadPlaceholderPlugin, { remove: { id: upload.id } })
|
||||
);
|
||||
@@ -119,7 +141,6 @@ const insertFiles = function (
|
||||
}
|
||||
|
||||
const [from, to] = result;
|
||||
const dimensions = await FileHelper.getVideoDimensions(upload.file);
|
||||
|
||||
if (view.isDestroyed) {
|
||||
return;
|
||||
@@ -133,8 +154,7 @@ const insertFiles = function (
|
||||
schema.nodes.video.create({
|
||||
src,
|
||||
title: upload.file.name ?? dictionary.untitled,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
...upload.dimensions,
|
||||
...options.attrs,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import extract from "png-chunks-extract";
|
||||
|
||||
export default class FileHelper {
|
||||
/**
|
||||
* Checks if a file is an image.
|
||||
@@ -31,6 +33,7 @@ export default class FileHelper {
|
||||
return new Promise((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
video.preload = "metadata";
|
||||
video.crossOrigin = "anonymous";
|
||||
video.onloadedmetadata = () => {
|
||||
window.URL.revokeObjectURL(video.src);
|
||||
resolve({ width: video.videoWidth, height: video.videoHeight });
|
||||
@@ -39,4 +42,60 @@ export default class FileHelper {
|
||||
video.src = URL.createObjectURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the dimensions of an image file – currently only PNGs are supported – but we mainly use
|
||||
* this to get the "real" dimensions of a retina image.
|
||||
*
|
||||
* @param file The file to load the dimensions for
|
||||
* @returns The dimensions of the image, if known.
|
||||
*/
|
||||
static async getImageDimensions(
|
||||
file: File
|
||||
): Promise<{ width: number; height: number } | undefined> {
|
||||
if (file.type !== "image/png") {
|
||||
return;
|
||||
}
|
||||
|
||||
function parsePhys(view: DataView) {
|
||||
return {
|
||||
ppux: view.getUint32(0),
|
||||
ppuy: view.getUint32(4),
|
||||
unit: view.getUint8(4),
|
||||
};
|
||||
}
|
||||
|
||||
function parseIHDR(view: DataView) {
|
||||
return {
|
||||
width: view.getUint32(0),
|
||||
height: view.getUint32(4),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = await file.arrayBuffer();
|
||||
const chunks = extract(new Uint8Array(buffer));
|
||||
const pHYsChunk = chunks.find((chunk) => chunk.name === "pHYs");
|
||||
const iHDRChunk = chunks.find((chunk) => chunk.name === "IHDR");
|
||||
|
||||
if (!pHYsChunk || !iHDRChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer));
|
||||
const physData = parsePhys(new DataView(pHYsChunk.data.buffer));
|
||||
|
||||
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
||||
const pixelRatio = Math.round(physData.ppux / 2834.5);
|
||||
return {
|
||||
width: idhrData.width / pixelRatio,
|
||||
height: idhrData.height / pixelRatio,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ const uploadPlaceholder = new Plugin({
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = URL.createObjectURL(action.add.file);
|
||||
img.width = action.add.dimensions?.width;
|
||||
img.height = action.add.dimensions?.height;
|
||||
|
||||
element.appendChild(img);
|
||||
|
||||
@@ -77,6 +79,8 @@ const uploadPlaceholder = new Plugin({
|
||||
video.src = URL.createObjectURL(action.add.file);
|
||||
video.autoplay = false;
|
||||
video.controls = false;
|
||||
video.width = action.add.dimensions?.width;
|
||||
video.height = action.add.dimensions?.height;
|
||||
|
||||
element.appendChild(video);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const uploadPlugin = (options: Options) =>
|
||||
}
|
||||
const pos = tr.selection.from;
|
||||
|
||||
insertFiles(view, event, pos, files, options);
|
||||
void insertFiles(view, event, pos, files, options);
|
||||
return true;
|
||||
},
|
||||
drop(view, event: DragEvent): boolean {
|
||||
@@ -71,7 +71,7 @@ const uploadPlugin = (options: Options) =>
|
||||
});
|
||||
|
||||
if (result) {
|
||||
insertFiles(view, event, result.pos, files, options);
|
||||
void insertFiles(view, event, result.pos, files, options);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export default class SimpleImage extends Node {
|
||||
inputElement.accept = AttachmentValidation.imageContentTypes.join(", ");
|
||||
inputElement.onchange = (event) => {
|
||||
const files = getEventFiles(event);
|
||||
insertFiles(view, event, state.selection.from, files, {
|
||||
void insertFiles(view, event, state.selection.from, files, {
|
||||
uploadFile,
|
||||
onFileUploadStart,
|
||||
onFileUploadStop,
|
||||
|
||||
Reference in New Issue
Block a user