utils.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module image/imageupload/utils
  7. */
  8. /* global fetch, File */
  9. import LabeledFieldView from '@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview';
  10. import { createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledfield/utils';
  11. /**
  12. * Creates a regular expression used to test for image files.
  13. *
  14. * const imageType = createImageTypeRegExp( [ 'png', 'jpeg', 'svg+xml', 'vnd.microsoft.icon' ] );
  15. *
  16. * console.log( 'is supported image', imageType.test( file.type ) );
  17. *
  18. * @param {Array.<String>} types
  19. * @returns {RegExp}
  20. */
  21. export function createImageTypeRegExp( types ) {
  22. // Sanitize the MIME type name which may include: "+", "-" or ".".
  23. const regExpSafeNames = types.map( type => type.replace( '+', '\\+' ) );
  24. return new RegExp( `^image\\/(${ regExpSafeNames.join( '|' ) })$` );
  25. }
  26. /**
  27. * Creates a promise that fetches the image local source (Base64 or blob) and resolves with a `File` object.
  28. *
  29. * @param {module:engine/view/element~Element} image Image whose source to fetch.
  30. * @returns {Promise.<File>} A promise which resolves when an image source is fetched and converted to a `File` instance.
  31. * It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
  32. */
  33. export function fetchLocalImage( image ) {
  34. return new Promise( ( resolve, reject ) => {
  35. const imageSrc = image.getAttribute( 'src' );
  36. // Fetch works asynchronously and so does not block browser UI when processing data.
  37. fetch( imageSrc )
  38. .then( resource => resource.blob() )
  39. .then( blob => {
  40. const mimeType = getImageMimeType( blob, imageSrc );
  41. const ext = mimeType.replace( 'image/', '' );
  42. const filename = `image.${ ext }`;
  43. const file = new File( [ blob ], filename, { type: mimeType } );
  44. resolve( file );
  45. } )
  46. .catch( reject );
  47. } );
  48. }
  49. /**
  50. * Checks whether a given node is an image element with a local source (Base64 or blob).
  51. *
  52. * @param {module:engine/view/node~Node} node The node to check.
  53. * @returns {Boolean}
  54. */
  55. export function isLocalImage( node ) {
  56. if ( !node.is( 'element', 'img' ) || !node.getAttribute( 'src' ) ) {
  57. return false;
  58. }
  59. return node.getAttribute( 'src' ).match( /^data:image\/\w+;base64,/g ) ||
  60. node.getAttribute( 'src' ).match( /^blob:/g );
  61. }
  62. // Extracts an image type based on its blob representation or its source.
  63. //
  64. // @param {String} src Image `src` attribute value.
  65. // @param {Blob} blob Image blob representation.
  66. // @returns {String}
  67. function getImageMimeType( blob, src ) {
  68. if ( blob.type ) {
  69. return blob.type;
  70. } else if ( src.match( /data:(image\/\w+);base64/ ) ) {
  71. return src.match( /data:(image\/\w+);base64/ )[ 1 ].toLowerCase();
  72. } else {
  73. // Fallback to 'jpeg' as common extension.
  74. return 'image/jpeg';
  75. }
  76. }
  77. /**
  78. * Creates an integrations object that will be passed to the
  79. * {@link module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView}.
  80. *
  81. * @param {module:core/editor/editor~Editor} editor The editor instance.
  82. *
  83. * @returns {Object.<String, module:ui/view~View>} The integrations object.
  84. */
  85. export function prepareIntegrations( editor ) {
  86. const panelItems = editor.config.get( 'image.upload.panel.items' );
  87. const imageUploadUIPlugin = editor.plugins.get( 'ImageUploadUI' );
  88. const PREDEFINED_INTEGRATIONS = {
  89. 'insertImageViaUrl': createLabeledInputView( editor.locale )
  90. };
  91. if ( !panelItems ) {
  92. return PREDEFINED_INTEGRATIONS;
  93. }
  94. // Prepares ckfinder component for the `openCKFinder` integration token.
  95. if ( panelItems.find( item => item === 'openCKFinder' ) && editor.ui.componentFactory.has( 'ckfinder' ) ) {
  96. const ckFinderButton = editor.ui.componentFactory.create( 'ckfinder' );
  97. ckFinderButton.set( {
  98. withText: true,
  99. class: 'ck-image-upload__ck-finder-button'
  100. } );
  101. // We want to close the dropdown panel view when user clicks the ckFinderButton.
  102. ckFinderButton.delegate( 'execute' ).to( imageUploadUIPlugin, 'cancel' );
  103. PREDEFINED_INTEGRATIONS.openCKFinder = ckFinderButton;
  104. }
  105. // Creates integrations object of valid views to pass it to the ImageUploadPanelView.
  106. return panelItems.reduce( ( object, key ) => {
  107. if ( PREDEFINED_INTEGRATIONS[ key ] ) {
  108. object[ key ] = PREDEFINED_INTEGRATIONS[ key ];
  109. } else if ( editor.ui.componentFactory.has( key ) ) {
  110. object[ key ] = editor.ui.componentFactory.create( key );
  111. }
  112. return object;
  113. }, {} );
  114. }
  115. /**
  116. * Creates a labeled field view.
  117. *
  118. * @param {module:utils/locale~Locale} locale The localization services instance.
  119. *
  120. * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
  121. */
  122. export function createLabeledInputView( locale ) {
  123. const t = locale.t;
  124. const labeledInputView = new LabeledFieldView( locale, createLabeledInputText );
  125. labeledInputView.set( {
  126. label: t( 'Insert image via URL' )
  127. } );
  128. labeledInputView.fieldView.placeholder = 'https://example.com/src/image.png';
  129. labeledInputView.infoText = t( 'Paste the image source URL.' );
  130. return labeledInputView;
  131. }