8
0

converters.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module image/image/converters
  7. */
  8. import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
  9. import first from '@ckeditor/ckeditor5-utils/src/first';
  10. /**
  11. * Returns a function that converts the image view representation:
  12. *
  13. * <figure class="image"><img src="..." alt="..."></img></figure>
  14. *
  15. * to the model representation:
  16. *
  17. * <image src="..." alt="..."></image>
  18. *
  19. * The entire content of the `<figure>` element except the first `<img>` is being converted as children
  20. * of the `<image>` model element.
  21. *
  22. * @returns {Function}
  23. */
  24. export function viewFigureToModel() {
  25. return ( evt, data, conversionApi ) => {
  26. // Do not convert if this is not an "image figure".
  27. if ( !conversionApi.consumable.test( data.viewItem, { name: true, class: 'image' } ) ) {
  28. return;
  29. }
  30. // Find an image element inside the figure element.
  31. const viewImage = Array.from( data.viewItem.getChildren() ).find( viewChild => viewChild.is( 'img' ) );
  32. // Do not convert if image element is absent, is missing src attribute or was already converted.
  33. if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
  34. return;
  35. }
  36. // Find allowed ancestor in model for image that is gong to be inserted.
  37. const splitResult = conversionApi.splitToAllowedParent( 'image', data.modelCursor );
  38. // Do not convert if image cannot be placed in model at current position and in any of ancestors.
  39. if ( !splitResult ) {
  40. return;
  41. }
  42. // Convert view image to model image and save result as a conversion result.
  43. // Image is an object, so we are sure that it won't be split so at this stage of conversion
  44. // modelCursor and modelRange ends after converted image.
  45. data = Object.assign( data, conversionApi.convertItem( viewImage, splitResult.position ) );
  46. // Consume figure from consumable list.
  47. conversionApi.consumable.consume( data.viewItem, { name: true, class: 'image' } );
  48. // Get image element from conversion result.
  49. const modelImage = first( data.modelRange.getItems() );
  50. // Convert rest of the figure element's children as an image children.
  51. conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( modelImage ) );
  52. // When parent of current modelCursor had to be split to insert image let's
  53. // continue conversion inside split element.
  54. if ( splitResult.cursorParent ) {
  55. data.modelCursor = ModelPosition.createAt( splitResult.cursorParent );
  56. }
  57. };
  58. }
  59. /**
  60. * Creates the image attribute converter for provided model conversion dispatchers.
  61. *
  62. * @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
  63. * @param {String} attributeName
  64. * @param {Function} [converter] Custom converter for the attribute - default one converts attribute from model `image` element
  65. * to the same attribute in `img` in the view.
  66. */
  67. export function createImageAttributeConverter( dispatchers, attributeName, converter = modelToViewAttributeConverter ) {
  68. for ( const dispatcher of dispatchers ) {
  69. dispatcher.on( `attribute:${ attributeName }:image`, converter() );
  70. }
  71. }
  72. /**
  73. * Converter used to convert `srcset` model image's attribute to `srcset`, `sizes` and `width` attributes in the view.
  74. *
  75. * @return {Function}
  76. */
  77. export function srcsetAttributeConverter() {
  78. return ( evt, data, consumable, conversionApi ) => {
  79. const parts = evt.name.split( ':' );
  80. const consumableType = parts[ 0 ] + ':' + parts[ 1 ];
  81. const modelImage = data.item;
  82. if ( !consumable.consume( modelImage, consumableType ) ) {
  83. return;
  84. }
  85. const figure = conversionApi.mapper.toViewElement( modelImage );
  86. const img = figure.getChild( 0 );
  87. if ( data.attributeNewValue === null ) {
  88. const srcset = data.attributeOldValue;
  89. if ( srcset.data ) {
  90. img.removeAttribute( 'srcset' );
  91. img.removeAttribute( 'sizes' );
  92. if ( srcset.width ) {
  93. img.removeAttribute( 'width' );
  94. }
  95. }
  96. } else {
  97. const srcset = data.attributeNewValue;
  98. if ( srcset.data ) {
  99. img.setAttribute( 'srcset', srcset.data );
  100. // Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2.
  101. img.setAttribute( 'sizes', '100vw' );
  102. if ( srcset.width ) {
  103. img.setAttribute( 'width', srcset.width );
  104. }
  105. }
  106. }
  107. };
  108. }
  109. // Returns model to view image converter converting given attribute, and adding it to `img` element nested inside `figure` element.
  110. //
  111. // @private
  112. function modelToViewAttributeConverter() {
  113. return ( evt, data, consumable, conversionApi ) => {
  114. const parts = evt.name.split( ':' );
  115. const consumableType = parts[ 0 ] + ':' + parts[ 1 ];
  116. const modelImage = data.item;
  117. if ( !consumable.consume( modelImage, consumableType ) ) {
  118. return;
  119. }
  120. const figure = conversionApi.mapper.toViewElement( modelImage );
  121. const img = figure.getChild( 0 );
  122. if ( data.attributeNewValue !== null ) {
  123. img.setAttribute( data.attributeKey, data.attributeNewValue );
  124. } else {
  125. img.removeAttribute( data.attributeKey );
  126. }
  127. };
  128. }