All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources._diffs.plugins.addimages.plugin.js Maven / Gradle / Ivy

/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

(function () {
	const isIE = CKEDITOR.env.ie;

	/**
	 * CKEditor plugin which allows Drag&Drop of images directly into the editable area. The image will be encoded
	 * as Data URI. An event `beforeImageAdd` will be fired with the list of dropped images. If any of the listeners
	 * returns `false` or cancels the event, the images won't be added to the content. Otherwise,
	 * an event `imageAdd` will be fired with the inserted element into the editable area.
	 *
	 * @class CKEDITOR.plugins.addimages
	 */

	/**
	 * Fired before adding images to the editor.
	 *
	 * @event CKEDITOR.plugins.addimages#beforeImageAdd
	 * @instance
	 * @memberof CKEDITOR.plugins.addimages
	 * @param {Array} imageFiles Array of image files
	 */

	/**
	 * Fired when an image is being added to the editor successfully.
	 *
	 * @event CKEDITOR.plugins.addimages#imageAdd
	 * @instance
	 * @memberof CKEDITOR.plugins.addimages
	 * @param {CKEDITOR.dom.element} el The created image with src as Data URI
	 * @param {File} file The image file
	 */

	CKEDITOR.plugins.add('addimages', {
		_fetchBlob(url) {
			return Liferay.Util.fetch(url).then((response) => response.blob());
		},

		/**
		 * Accepts an array of dropped files to the editor. Then, it filters the images and sends them for further
		 * processing to {{#crossLink "CKEDITOR.plugins.addimages/_processFile:method"}}{{/crossLink}}
		 *
		 * @fires CKEDITOR.plugins.addimages#beforeImageAdd
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _handleFiles
		 * @param {Array.} files Array of dropped files. Only the images from this list will be processed
		 * @param {CKEDITOR.editor} editor The current editor instance
		 * @protected
		 */
		_handleFiles(files, editor) {
			let file;
			let i;

			const imageFiles = [];

			for (i = 0; i < files.length; i++) {
				file = files[i];

				if (file.type.indexOf('image') === 0) {
					imageFiles.push(file);
				}
			}

			const result = editor.fire('beforeImageAdd', {
				imageFiles,
			});

			if (result) {
				for (i = 0; i < imageFiles.length; i++) {
					file = imageFiles[i];

					this._processFile(file, editor);
				}
			}

			return false;
		},

		/**
		 * Handles drop event. The function will create a selection from the current
		 * point and will send a list of files to be processed to
		 * {{#crossLink "CKEDITOR.plugins.addimages/_handleFiles:method"}}{{/crossLink}} method.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _onDragDrop
		 * @param {CKEDITOR.eventInfo} Facade for the native `drop` event
		 * @protected
		 */
		_onDragDrop(event) {
			const nativeEvent = event.data.$;

			const transferFiles = nativeEvent.dataTransfer.files;

			if (transferFiles.length) {
				new CKEDITOR.dom.event(nativeEvent).preventDefault();

				const editor = event.listenerData.editor;

				this._handleFiles(transferFiles, editor);
			}
		},

		/**
		 * Handles dragenter event. In case of IE, this function will prevent the event.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _onDragEnter
		 * @param {CKEDITOR.eventInfo} Facade for the native `dragenter` event
		 * @protected
		 */
		_onDragEnter(event) {
			if (isIE) {
				this._preventEvent(event);
			}
		},

		/**
		 * Handles dragover event. In case of IE, this function will prevent the event.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _onDragOver
		 * @param {CKEDITOR.eventInfo} event Facade for the native `dragover` event
		 * @protected
		 */
		_onDragOver(event) {
			if (isIE) {
				this._preventEvent(event);
			}
		},

		/**
		 * Handler for when images are uploaded
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _onImageUploaded
		 * @param {Image} image The image that was uploaded
		 * @param {CKEDITOR.editor} editor The current editor instance
		 * @protected
		 */
		_onImageUploaded(image, editor) {
			const instance = this;

			const fragment = CKEDITOR.htmlParser.fragment.fromHtml(
				editor.getData()
			);

			const filter = new CKEDITOR.htmlParser.filter({
				elements: {

					// eslint-disable-next-line @liferay/no-abbreviations
					img(element) {
						if (image.src === instance._tempImage.src) {
							element.attributes.src = image.src;
						}
					},
				},
			});

			const writer = new CKEDITOR.htmlParser.basicWriter();

			fragment.writeChildrenHtml(writer, filter);

			editor.setData(writer.getHtml(), () => {
				editor.updateElement();
			});
		},

		/**
		 * Checks if the pasted data is a dropped image or html.
		 * In the case of images, it passes it to
		 * {{#crossLink "CKEDITOR.plugins.addimages/_processFile:method"}}{{/crossLink}} for processing.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _onPaste
		 * @param {CKEDITOR.eventInfo} event Facade for the native `paste` event
		 * @protected
		 */
		_onPaste(event) {
			if (!event.data?.$?.clipboardData?.items) {
				return;
			}

			const editor = event.listenerData.editor;

			const clipboardItem = event.data.$.clipboardData.items[0];

			if (clipboardItem.type.indexOf('image') === 0) {
				const imageFile = clipboardItem.getAsFile();

				this._processFile(imageFile, editor);
			}
			else if (clipboardItem.type === 'html') {
				const fragment = CKEDITOR.htmlParser.fragment.fromHtml(
					event.data.dataValue
				);

				fragment.forEach((element) => {
					if (
						element.type !== CKEDITOR.NODE_ELEMENT ||
						element.name !== 'img'
					) {
						return;
					}

					const base64Regex = /^data:image\/(.*);base64,/;
					const match = element.attributes.src.match(base64Regex);

					if (element.attributes.src && match) {
						const src = element.attributes.src;
						const extension = match[1];
						const name = `${Date.now().toString()}.${extension}`;

						this._fetchBlob(src)
							.then((blob) => {
								const file = new File([blob], name, {
									type: blob.type,
								});

								const element =
									CKEDITOR.dom.element.createFromHtml(
										``
									);

								editor.fire('imageAdd', {
									element,
									file,
								});
							})
							.catch(() => {
								Liferay.Util.openToast({
									message: Liferay.Language.get(
										'an-unexpected-error-occurred-while-uploading-your-file'
									),
									type: 'danger',
								});
							});
					}
				});
			}
		},

		/**
		 * Prevents a native event.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _preventEvent
		 * @param {CKEDITOR.eventInfo} event Facade for the native event to be prevented
		 * @protected
		 */
		_preventEvent(event) {
			event = new CKEDITOR.dom.event(event.data.$);

			event.preventDefault();
			event.stopPropagation();
		},

		/**
		 * Processes an image file. The function creates an img element and sets as source
		 * a Data URI, then fires an 'imageAdd' event via CKEditor's event system.
		 *
		 * @fires CKEDITOR.plugins.addimages#imageAdd
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method _processFile
		 * @param {File} file The file to be processed
		 * @param {CKEDITOR.editor} editor The current editor instance
		 * @protected
		 */
		_processFile(file, editor) {
			const reader = new FileReader();

			reader.addEventListener('loadend', () => {
				const bin = reader.result;

				const element = CKEDITOR.dom.element.createFromHtml(
					''
				);

				editor.insertElement(element);

				const imageData = {
					element,
					file,
				};

				editor.fire('imageAdd', imageData);
			});

			reader.readAsDataURL(file);
		},

		/**
		 * Initialization of the plugin, part of CKEditor plugin lifecycle.
		 * The function registers a 'dragenter', 'dragover', 'drop' and `paste` events on the editing area.
		 *
		 * @instance
		 * @memberof CKEDITOR.plugins.addimages
		 * @method init
		 * @param {CKEDITOR.editor} editor The current editor instance
		 */
		init(editor) {
			editor.once('contentDom', () => {
				const editable = editor.editable();

				editable.attachListener(
					editable,
					'dragenter',
					this._onDragEnter,
					this,
					{
						editor,
					}
				);

				editable.attachListener(
					editable,
					'dragover',
					this._onDragOver,
					this,
					{
						editor,
					}
				);

				editable.attachListener(
					editable,
					'drop',
					this._onDragDrop,
					this,
					{
						editor,
					}
				);

				editable.attachListener(
					editable,
					'paste',
					this._onPaste,
					this,
					{
						editor,
					}
				);
			});

			AUI().use('aui-progressbar', 'uploader', (A) => {
				const ATTR_DATA_RANDOM_ID = 'data-random-id';
				const CSS_UPLOADING_IMAGE = 'uploading-image';

				const TPL_IMAGE_CONTAINER =
					'
'; const TPL_PROGRESS_BAR = '
'; const ckeditorImage = document.querySelector( 'img[data-cke-saved-src^="data:image"]:not([data-fileentryid])' ); const _onUploadError = () => { const image = this._tempImage; if (ckeditorImage) { ckeditorImage.remove(); } if (image) { image.parentElement.remove(); } Liferay.Util.openToast({ message: Liferay.Language.get( 'an-unexpected-error-occurred-while-uploading-your-file' ), type: 'danger', }); }; const _onUploadComplete = (event) => { const target = event.details[0].target; const progressbar = target.progressbar; if (progressbar) { progressbar.destroy(); } const data = JSON.parse(event.data); if (data.success) { const image = this._tempImage; if (ckeditorImage) { ckeditorImage.remove(); } if (image) { image.removeAttribute(ATTR_DATA_RANDOM_ID); image.classList.remove(CSS_UPLOADING_IMAGE); image.setAttribute( data.file.attributeDataImageId, data.file.fileEntryId ); image.src = editor.config.attachmentURLPrefix ? editor.config.attachmentURLPrefix + data.file.title : data.file.url; const imageContainer = image.parentElement; A.one(image).unwrap(imageContainer); imageContainer.remove(); editor.fire('imageUploaded', { editor, // eslint-disable-next-line @liferay/no-abbreviations el: image, fileEntryId: data.file.fileEntryId, uploadImageReturnType: '', }); const fragment = CKEDITOR.htmlParser.fragment.fromHtml( editor.getData() ); let imageFound = false; fragment.forEach((element) => { if ( element.type === CKEDITOR.NODE_ELEMENT && element.attributes['data-image-id'] === image.dataset.imageId ) { imageFound = true; } }); if (!imageFound) { this._onImageUploaded(image, editor); } } } else { _onUploadError(); } }; const _onUploadProgress = (event) => { const percentLoaded = Math.round(event.percentLoaded); const target = event.details[0].target; const progressbar = target.progressbar; if (progressbar) { progressbar.set('label', percentLoaded + ' %'); progressbar.set('value', Math.ceil(percentLoaded)); } }; const _createProgressBar = (image) => { const imageContainerNode = A.Node.create(TPL_IMAGE_CONTAINER); const progressBarNode = A.Node.create(TPL_PROGRESS_BAR); A.one(image).wrap(imageContainerNode); imageContainerNode.appendChild(progressBarNode); const progressbar = new A.ProgressBar({ boundingBox: progressBarNode, }).render(); return progressbar; }; editor.on('imageAdd', (event) => { const eventData = event.data; let file = eventData.file; const image = eventData.element.$; const randomId = eventData.randomId || A.guid(); image.setAttribute(ATTR_DATA_RANDOM_ID, randomId); image.classList.add(CSS_UPLOADING_IMAGE); this._tempImage = image; let uploader = eventData.uploader; if (uploader) { uploader.on('uploadcomplete', _onUploadComplete); uploader.on('uploaderror', _onUploadError); uploader.on('uploadprogress', _onUploadProgress); } else { file = new A.FileHTML5(file); uploader = new A.Uploader({ fileFieldName: 'imageSelectorFileName', uploadURL: editor.config.uploadUrl, }); uploader.on('uploadcomplete', _onUploadComplete); uploader.on('uploaderror', _onUploadError); uploader.on('uploadprogress', _onUploadProgress); uploader.set('postVarsPerFile', { randomId, }); uploader.upload(file); } file.progressbar = _createProgressBar(image); }); }); }, }); })();




© 2015 - 2025 Weber Informatics LLC | Privacy Policy