| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- /**
- * @module image/imageupload/imageuploadediting
- */
- import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
- import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
- import ImageUploadCommand from '../../src/imageupload/imageuploadcommand';
- import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
- import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
- import { isImageType, findOptimalInsertionPosition } from '../../src/imageupload/utils';
- /**
- * Image upload editing plugin.
- *
- * @extends module:core/plugin~Plugin
- */
- export default class ImageUploadEditing extends Plugin {
- /**
- * @inheritDoc
- */
- static get requires() {
- return [ FileRepository, Notification ];
- }
- /**
- * @inheritDoc
- */
- init() {
- const editor = this.editor;
- const doc = editor.model.document;
- const schema = editor.model.schema;
- const fileRepository = editor.plugins.get( FileRepository );
- // Setup schema to allow uploadId and uploadStatus for images.
- schema.extend( 'image', {
- allowAttributes: [ 'uploadId', 'uploadStatus' ]
- } );
- // Register imageUpload command.
- editor.commands.add( 'imageUpload', new ImageUploadCommand( editor ) );
- // Execute imageUpload command when image is dropped or pasted.
- editor.editing.view.on( 'clipboardInput', ( evt, data ) => {
- // Skip if non empty HTML data is included.
- // https://github.com/ckeditor/ckeditor5-upload/issues/68
- if ( isHtmlIncluded( data.dataTransfer ) ) {
- return;
- }
- let targetModelSelection = new ModelSelection(
- data.targetRanges.map( viewRange => editor.editing.mapper.toModelRange( viewRange ) )
- );
- for ( const file of data.dataTransfer.files ) {
- const insertAt = findOptimalInsertionPosition( targetModelSelection );
- if ( isImageType( file ) ) {
- editor.execute( 'imageUpload', { file, insertAt } );
- evt.stop();
- }
- // Use target ranges only for the first image. Then, use that image position
- // so we keep adding the next ones after the previous one.
- targetModelSelection = doc.selection;
- }
- } );
- // Prevents from browser redirecting to the dropped image.
- editor.editing.view.on( 'dragover', ( evt, data ) => {
- data.preventDefault();
- } );
- doc.on( 'change', () => {
- const changes = doc.differ.getChanges( { includeChangesInGraveyard: true } );
- for ( const entry of changes ) {
- if ( entry.type == 'insert' && entry.name == 'image' ) {
- const item = entry.position.nodeAfter;
- const isInGraveyard = entry.position.root.rootName == '$graveyard';
- // Check if the image element still has upload id.
- const uploadId = item.getAttribute( 'uploadId' );
- if ( !uploadId ) {
- continue;
- }
- // Check if the image is loaded on this client.
- const loader = fileRepository.loaders.get( uploadId );
- if ( !loader ) {
- continue;
- }
- if ( isInGraveyard ) {
- // If the image was inserted to the graveyard - abort the loading process.
- loader.abort();
- } else if ( loader.status == 'idle' ) {
- // If the image was inserted into content and has not been loaded, start loading it.
- this._load( loader, item );
- }
- }
- }
- } );
- }
- /**
- * Performs image loading. Image is read from the disk and temporary data is displayed, after uploading process
- * is complete we replace temporary data with target image from the server.
- *
- * @private
- * @param {module:upload/filerepository~FileLoader} loader
- * @param {module:engine/model/element~Element} imageElement
- */
- _load( loader, imageElement ) {
- const editor = this.editor;
- const model = editor.model;
- const t = editor.locale.t;
- const fileRepository = editor.plugins.get( FileRepository );
- const notification = editor.plugins.get( Notification );
- model.enqueueChange( 'transparent', writer => {
- writer.setAttribute( 'uploadStatus', 'reading', imageElement );
- } );
- loader.read()
- .then( data => {
- const viewFigure = editor.editing.mapper.toViewElement( imageElement );
- const viewImg = viewFigure.getChild( 0 );
- const promise = loader.upload();
- viewImg.setAttribute( 'src', data );
- editor.editing.view.render();
- model.enqueueChange( 'transparent', writer => {
- writer.setAttribute( 'uploadStatus', 'uploading', imageElement );
- } );
- return promise;
- } )
- .then( data => {
- model.enqueueChange( 'transparent', writer => {
- writer.setAttributes( { uploadStatus: 'complete', src: data.default }, imageElement );
- // Srcset attribute for responsive images support.
- let maxWidth = 0;
- const srcsetAttribute = Object.keys( data )
- // Filter out keys that are not integers.
- .filter( key => {
- const width = parseInt( key, 10 );
- if ( !isNaN( width ) ) {
- maxWidth = Math.max( maxWidth, width );
- return true;
- }
- } )
- // Convert each key to srcset entry.
- .map( key => `${ data[ key ] } ${ key }w` )
- // Join all entries.
- .join( ', ' );
- if ( srcsetAttribute != '' ) {
- writer.setAttribute( 'srcset', {
- data: srcsetAttribute,
- width: maxWidth
- }, imageElement );
- }
- } );
- clean();
- } )
- .catch( msg => {
- // Might be 'aborted'.
- if ( loader.status == 'error' ) {
- notification.showWarning( msg, {
- title: t( 'Upload failed' ),
- namespace: 'upload'
- } );
- }
- clean();
- // Permanently remove image from insertion batch.
- model.enqueueChange( 'transparent', writer => {
- writer.remove( imageElement );
- } );
- } );
- function clean() {
- model.enqueueChange( 'transparent', writer => {
- writer.removeAttribute( 'uploadId', imageElement );
- writer.removeAttribute( 'uploadStatus', imageElement );
- } );
- fileRepository.destroyLoader( loader );
- }
- }
- }
- // Returns true if non-empty `text/html` is included in data transfer.
- //
- // @param {module:clipboard/datatransfer~DataTransfer} dataTransfer
- // @returns {Boolean}
- export function isHtmlIncluded( dataTransfer ) {
- return Array.from( dataTransfer.types ).includes( 'text/html' ) && dataTransfer.getData( 'text/html' ) !== '';
- }
|