8
0

imageuploadengine.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module upload/imageuploadengine
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import FileRepository from './filerepository';
  10. import ImageUploadCommand from './imageuploadcommand';
  11. import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
  12. import { isImageType } from './utils';
  13. /**
  14. * Image upload engine plugin.
  15. *
  16. * @extends module:core/plugin~Plugin
  17. */
  18. export default class ImageUploadEngine extends Plugin {
  19. /**
  20. * @inheritDoc
  21. */
  22. static get requires() {
  23. return [ FileRepository, Notification ];
  24. }
  25. /**
  26. * @inheritDoc
  27. */
  28. init() {
  29. const editor = this.editor;
  30. const doc = editor.document;
  31. const schema = doc.schema;
  32. const fileRepository = editor.plugins.get( FileRepository );
  33. // Setup schema to allow uploadId for images.
  34. schema.allow( { name: 'image', attributes: [ 'uploadId' ], inside: '$root' } );
  35. schema.allow( { name: 'image', attributes: [ 'uploadStatus' ], inside: '$root' } );
  36. schema.requireAttributes( 'image', [ 'uploadId' ] );
  37. // Register imageUpload command.
  38. editor.commands.add( 'imageUpload', new ImageUploadCommand( editor ) );
  39. // Execute imageUpload command when image is dropped or pasted.
  40. editor.editing.view.on( 'clipboardInput', ( evt, data ) => {
  41. for ( const file of data.dataTransfer.files ) {
  42. if ( isImageType( file ) ) {
  43. editor.execute( 'imageUpload', { file } );
  44. evt.stop();
  45. }
  46. }
  47. } );
  48. doc.on( 'change', ( evt, type, data ) => {
  49. // Listen on document changes and:
  50. // * start upload process when image with `uploadId` attribute is inserted,
  51. // * abort upload process when image `uploadId` attribute is removed.
  52. if ( type === 'insert' || type === 'reinsert' || type === 'remove' ) {
  53. for ( const value of data.range ) {
  54. if ( value.type === 'elementStart' && value.item.name === 'image' ) {
  55. const imageElement = value.item;
  56. const uploadId = imageElement.getAttribute( 'uploadId' );
  57. if ( uploadId ) {
  58. const loader = fileRepository.loaders.get( uploadId );
  59. if ( loader ) {
  60. if ( type === 'insert' && loader.status == 'idle' ) {
  61. this.load( loader, imageElement );
  62. }
  63. if ( type === 'remove' ) {
  64. loader.abort();
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. } );
  72. }
  73. /**
  74. * Performs image loading. Image is read from the disk and temporary data is displayed, after uploading process
  75. * is complete we replace temporary data with target image from the server.
  76. *
  77. * @protected
  78. * @param {module:upload/filerepository~FileLoader} loader
  79. * @param {module:engine/model/element~Element} imageElement
  80. */
  81. load( loader, imageElement ) {
  82. const editor = this.editor;
  83. const t = editor.locale.t;
  84. const doc = editor.document;
  85. const fileRepository = editor.plugins.get( FileRepository );
  86. const notification = editor.plugins.get( Notification );
  87. doc.enqueueChanges( () => {
  88. doc.batch( 'transparent' ).setAttribute( imageElement, 'uploadStatus', 'reading' );
  89. } );
  90. loader.read()
  91. .then( data => {
  92. const viewFigure = editor.editing.mapper.toViewElement( imageElement );
  93. const viewImg = viewFigure.getChild( 0 );
  94. const promise = loader.upload();
  95. viewImg.setAttribute( 'src', data );
  96. editor.editing.view.render();
  97. doc.enqueueChanges( () => {
  98. doc.batch( 'transparent' ).setAttribute( imageElement, 'uploadStatus', 'uploading' );
  99. } );
  100. return promise;
  101. } )
  102. .then( data => {
  103. doc.enqueueChanges( () => {
  104. doc.batch( 'transparent' ).setAttribute( imageElement, 'uploadStatus', 'complete' );
  105. doc.batch( 'transparent' ).setAttribute( imageElement, 'src', data.original );
  106. // Srcset attribute for responsive images support.
  107. const srcsetAttribute = Object.keys( data )
  108. // Filter out keys that are not integers.
  109. .filter( key => !isNaN( parseInt( key, 10 ) ) )
  110. // Convert each key to srcset entry.
  111. .map( key => `${ data[ key ] } ${ key }w` )
  112. // Join all entries.
  113. .join( ', ' );
  114. if ( srcsetAttribute != '' ) {
  115. doc.batch( 'transparent' ).setAttribute( imageElement, 'srcset', srcsetAttribute );
  116. }
  117. } );
  118. clean();
  119. } )
  120. .catch( msg => {
  121. // Might be 'aborted'.
  122. if ( loader.status == 'error' ) {
  123. notification.showWarning( msg, {
  124. title: t( 'Upload failed' ),
  125. namespace: 'upload'
  126. } );
  127. }
  128. clean();
  129. // Permanently remove image from insertion batch.
  130. doc.enqueueChanges( () => {
  131. doc.batch( 'transparent' ).remove( imageElement );
  132. } );
  133. } );
  134. function clean() {
  135. doc.enqueueChanges( () => {
  136. doc.batch( 'transparent' ).removeAttribute( imageElement, 'uploadId' );
  137. doc.batch( 'transparent' ).removeAttribute( imageElement, 'uploadStatus' );
  138. } );
  139. fileRepository.destroyLoader( loader );
  140. }
  141. }
  142. }