@ckeditor/ckeditor5-image
Version:
Image feature for CKEditor 5.
113 lines (112 loc) • 4.7 kB
JavaScript
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { global } from 'ckeditor5/src/utils';
/**
* Creates a regular expression used to test for image files.
*
* ```ts
* const imageType = createImageTypeRegExp( [ 'png', 'jpeg', 'svg+xml', 'vnd.microsoft.icon' ] );
*
* console.log( 'is supported image', imageType.test( file.type ) );
* ```
*/
export function createImageTypeRegExp(types) {
// Sanitize the MIME type name which may include: "+", "-" or ".".
const regExpSafeNames = types.map(type => type.replace('+', '\\+'));
return new RegExp(`^image\\/(${regExpSafeNames.join('|')})$`);
}
/**
* Creates a promise that fetches the image local source (Base64 or blob) and resolves with a `File` object.
*
* @param image Image whose source to fetch.
* @returns A promise which resolves when an image source is fetched and converted to a `File` instance.
* It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
*/
export function fetchLocalImage(image) {
return new Promise((resolve, reject) => {
const imageSrc = image.getAttribute('src');
// Fetch works asynchronously and so does not block browser UI when processing data.
fetch(imageSrc)
.then(resource => resource.blob())
.then(blob => {
const mimeType = getImageMimeType(blob, imageSrc);
const ext = mimeType.replace('image/', '');
const filename = `image.${ext}`;
const file = new File([blob], filename, { type: mimeType });
resolve(file);
})
.catch(err => {
// Fetch fails only, if it can't make a request due to a network failure or if anything prevented the request
// from completing, i.e. the Content Security Policy rules. It is not possible to detect the exact cause of failure,
// so we are just trying the fallback solution, if general TypeError is thrown.
return err && err.name === 'TypeError' ?
convertLocalImageOnCanvas(imageSrc).then(resolve).catch(reject) :
reject(err);
});
});
}
/**
* Checks whether a given node is an image element with a local source (Base64 or blob).
*
* @param node The node to check.
*/
export function isLocalImage(imageUtils, node) {
if (!imageUtils.isInlineImageView(node) || !node.getAttribute('src')) {
return false;
}
return !!node.getAttribute('src').match(/^data:image\/\w+;base64,/g) ||
!!node.getAttribute('src').match(/^blob:/g);
}
/**
* Extracts an image type based on its blob representation or its source.
* @param blob Image blob representation.
* @param src Image `src` attribute value.
*/
function getImageMimeType(blob, src) {
if (blob.type) {
return blob.type;
}
else if (src.match(/data:(image\/\w+);base64/)) {
return src.match(/data:(image\/\w+);base64/)[1].toLowerCase();
}
else {
// Fallback to 'jpeg' as common extension.
return 'image/jpeg';
}
}
/**
* Creates a promise that converts the image local source (Base64 or blob) to a blob using canvas and resolves
* with a `File` object.
* @param imageSrc Image `src` attribute value.
* @returns A promise which resolves when an image source is converted to a `File` instance.
* It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
*/
function convertLocalImageOnCanvas(imageSrc) {
return getBlobFromCanvas(imageSrc).then(blob => {
const mimeType = getImageMimeType(blob, imageSrc);
const ext = mimeType.replace('image/', '');
const filename = `image.${ext}`;
return new File([blob], filename, { type: mimeType });
});
}
/**
* Creates a promise that resolves with a `Blob` object converted from the image source (Base64 or blob).
* @param imageSrc Image `src` attribute value.
*/
function getBlobFromCanvas(imageSrc) {
return new Promise((resolve, reject) => {
const image = global.document.createElement('img');
image.addEventListener('load', () => {
const canvas = global.document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
canvas.toBlob(blob => blob ? resolve(blob) : reject());
});
image.addEventListener('error', () => reject());
image.src = imageSrc;
});
}