ckeditor5-1.0.x-dev/js/drupal/src/drupalImage/src/drupalimageediting.js

js/drupal/src/drupalImage/src/drupalimageediting.js
import { Plugin } from 'ckeditor5/src/core';

export default class DrupalImageEditing extends Plugin {
  /**
   * @inheritdoc
   */
  static get pluginName() {
    return 'DrupalImageEditing';
  }

  /**
   * @inheritdoc
   */
  init() {
    const editor = this.editor;
    const conversion = editor.conversion;
    const { schema } = editor.model;

    if ( schema.isRegistered( 'imageInline' ) ) {

      schema.extend( 'imageInline', {
        allowAttributes: [
          'dataEntityUuid',
          'dataEntityType',
          'width',
          'height',
        ]
      } );
    }

    if ( schema.isRegistered( 'imageBlock' ) ) {
      schema.extend( 'imageBlock', {
        allowAttributes: [
          'dataEntityUuid',
          'dataEntityType',
          'width',
          'height',
        ]
      } );
    }

    // Conversion.
    conversion.for( 'upcast' )
      .add( viewImageToModelImage( editor ) )
      .attributeToAttribute( {
        view: {
          name: 'img',
          key: 'width',
        },
        model: {
          key: 'width',
          value: viewElement => {
            return viewElement.getAttribute('width') + 'px';
          }
        }
      } )
      .attributeToAttribute( {
        view: {
          name: 'img',
          key: 'height',
        },
        model: {
          key: 'height',
          value: viewElement => {
            return viewElement.getAttribute('height') + 'px';
          }
        }
      });

    conversion.for( 'downcast' )
      .add( modelEntityUuidToDataAttribute() )
      .add( modelEntityTypeToDataAttribute() );

    conversion.for( 'dataDowncast' )
      .add( dispatcher => {
        dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => {
            if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {
              return;
            }

            let captionText = '';

            for ( const { item } of editor.model.createRangeIn( data.item ) ) {
              if ( !conversionApi.consumable.consume( item, 'insert' ) ) {
                continue;
              }

              if ( item.is( '$textProxy' ) ) {
                captionText += item.data;
              }
            }

            if ( captionText ) {
              const imageViewElement = conversionApi.mapper.toViewElement( data.item.parent );

              conversionApi.writer.setAttribute( 'data-caption', captionText, imageViewElement );
            }
          },
          { priority: 'high' }
        );
      } )
      .add( dispatcher => {
        dispatcher.on( 'insert:$text', ( evt, data ) => {
            const { parent } = data.item;
            const isInImageCaption = parent.is( 'element', 'caption' ) && parent.parent.is( 'element', 'imageBlock' );

            if ( isInImageCaption ) {
              // Prevent `modelViewSplitOnInsert()` function inside
              // ckeditor5-list package from interfering when downcasting a text
              // inside caption. Normally aforementioned function tries to
              // mitigate side effects of inserting content in the middle of the
              // lists, but in this case we want to stop the conversion from
              // proceeding.
              evt.stop();
            }
          },
          // Make sure we are overriding the `modelViewSplitOnInsert() converter
          // from ckeditor5-list.
          { priority: 'highest' }
        );
      } )
      .elementToElement( {
        model: 'imageBlock',
        view: ( modelElement, { writer } ) => createImageViewElement( writer, 'imageBlock' ),
        converterPriority: 'high'
      } )
      .elementToElement( {
        model: 'imageInline',
        view: ( modelElement, { writer } ) => createImageViewElement( writer, 'imageInline' ),
        converterPriority: 'high'
      } )
      .add( modelImageStyleToDataAttribute() )
      .add ( modelImageWidthToAttribute() )
      .add ( modelImageHeightToAttribute() );
  }
}

function viewImageToModelImage( editor ) {
  return dispatcher => {
    dispatcher.on( 'element:img', converter, { priority: 'high' } );
  };

  function converter( evt, data, conversionApi ) {
    const { viewItem } = data;
    const { writer, consumable, safeInsert, updateConversionResult, schema } = conversionApi;
    const attributesToConsume = [];

    let image;

    // Not only check if a given `img` view element has been consumed, but also
    // verify it has `src` attribute present.
    if ( !consumable.test( viewItem, { name: true, attributes: 'src' } ) ) {
      return;
    }

    // Create image that's allowed in the given context.
    if ( schema.checkChild( data.modelCursor, 'imageInline' ) ) {
      image = writer.createElement( 'imageInline', { src: viewItem.getAttribute( 'src' ) } );
    } else {
      image = writer.createElement( 'imageBlock', { src: viewItem.getAttribute( 'src' ) } );
    }

    if ( editor.plugins.has( 'ImageStyleEditing' ) &&
      consumable.test( viewItem, { name: true, attributes: 'data-align' } )
    ) {
      // https://ckeditor.com/docs/ckeditor5/latest/api/module_image_imagestyle_utils.html#constant-defaultStyles
      const dataToPresentationMapBlock = {
        left: 'alignBlockLeft',
        center: 'alignCenter',
        right: 'alignBlockRight'
      };
      const dataToPresentationMapInline = {
        left: 'alignLeft',
        right: 'alignRight'
      };

      const dataAlign = viewItem.getAttribute( 'data-align' );
      const alignment = image.is( 'element', 'imageBlock' ) ?
        dataToPresentationMapBlock[ dataAlign ] :
        dataToPresentationMapInline[ dataAlign ];

      writer.setAttribute( 'imageStyle', alignment, image );

      // Make sure the attribute can be consumed after successful `safeInsert`
      // operation.
      attributesToConsume.push( 'data-align' );
    }

    // Check if the view element has still unconsumed `data-caption` attribute.
    // Also, we can add caption only to block image.
    if ( image.is( 'element', 'imageBlock' ) &&
      consumable.test( viewItem, { name: true, attributes: 'data-caption' } )
    ) {
      // Create `caption` model element. Thanks to that element the rest of the
      // `ckeditor5-plugin` converters can recognize this image as a block image
      // with a caption.
      const caption = writer.createElement( 'caption' );

      writer.insertText( viewItem.getAttribute( 'data-caption' ), caption );

      // Insert the caption element into image, as a last child.
      writer.append( caption, image );

      // Make sure the attribute can be consumed after successful `safeInsert`
      // operation.
      attributesToConsume.push( 'data-caption' );
    }

    if ( consumable.test( viewItem, { name: true, attributes: 'data-entity-uuid' } ) ) {
      writer.setAttribute( 'dataEntityUuid', viewItem.getAttribute( 'data-entity-uuid' ), image );
      attributesToConsume.push( 'data-entity-uuid' );
    }

    if ( consumable.test( viewItem, { name: true, attributes: 'data-entity-type' } ) ) {
      writer.setAttribute( 'dataEntityFile', viewItem.getAttribute( 'data-entity-type' ), image );
      attributesToConsume.push( 'data-entity-type' );
    }

    // Try to place the image in the allowed position.
    if ( !safeInsert( image, data.modelCursor ) ) {
      return;
    }

    // Mark given element as consumed. Now other converters will not process it
    // anymore.
    consumable.consume( viewItem, { name: true, attributes: attributesToConsume } );

    // Make sure `modelRange` and `modelCursor` is up to date after inserting
    // new nodes into the model.
    updateConversionResult( image, data );
  }
}

function createImageViewElement( writer ) {
  return writer.createEmptyElement( 'img' );
}

function modelEntityUuidToDataAttribute() {
  return dispatcher => {
    dispatcher.on( 'attribute:dataEntityUuid', converter );
  };

  function converter( evt, data, conversionApi ) {
    const { item } = data;
    const { consumable, writer } = conversionApi;

    if ( !consumable.consume( item, evt.name ) ) {
      return;
    }

    const viewElement = conversionApi.mapper.toViewElement( item );
    const imageInFigure = Array.from( viewElement.getChildren() ).find( child => child.name === 'img' );

    writer.setAttribute( 'data-entity-uuid', data.attributeNewValue, imageInFigure || viewElement );
  }
}

function modelEntityTypeToDataAttribute() {
  return dispatcher => {
    dispatcher.on( 'attribute:dataEntityType', converter );
  };

  function converter( evt, data, conversionApi ) {
    const { item } = data;
    const { consumable, writer } = conversionApi;

    if ( !consumable.consume( item, evt.name ) ) {
      return;
    }

    const viewElement = conversionApi.mapper.toViewElement( item );
    const imageInFigure = Array.from( viewElement.getChildren() ).find( child => child.name === 'img' );

    writer.setAttribute( 'data-entity-type', data.attributeNewValue, imageInFigure || viewElement );
  }
}

function modelImageStyleToDataAttribute() {
  return dispatcher => {
    dispatcher.on( 'attribute:imageStyle', converter, { priority: 'high' } );
  };

  function converter( evt, data, conversionApi ) {
    const { item } = data;
    const { consumable, writer } = conversionApi;

    const mapping = {
      alignLeft: 'left',
      alignRight: 'right',
      alignCenter: 'center',
      alignBlockRight: 'right',
      alignBlockLeft: 'left',
    };

    // Consume only for the values that can be converted into data-align.
    if ( !mapping[data.attributeNewValue] || !consumable.consume( item, evt.name ) ) {
      return;
    }

    const viewElement = conversionApi.mapper.toViewElement( item );
    const imageInFigure = Array.from( viewElement.getChildren() ).find( child => child.name === 'img' );


    writer.setAttribute( 'data-align', mapping[data.attributeNewValue], imageInFigure || viewElement );
  }
}

function modelImageWidthToAttribute() {
  return dispatcher => {
    dispatcher.on('attribute:width:imageInline', converter, { priority: 'high' });
    dispatcher.on('attribute:width:imageBlock', converter, { priority: 'high' });
  };

  function converter(evt, data, conversionApi) {
    const { item } = data;
    const { consumable, writer } = conversionApi;

    if (!consumable.consume(item, evt.name) ) {
      return;
    }

    const viewElement = conversionApi.mapper.toViewElement(item);
    const imageInFigure = Array.from(viewElement.getChildren()).find(child => child.name === 'img');

    writer.setAttribute('width', data.attributeNewValue.replace('px', ''), imageInFigure || viewElement);
  }
}

function modelImageHeightToAttribute() {
  return dispatcher => {
    dispatcher.on('attribute:height:imageInline', converter, { priority: 'high' });
    dispatcher.on('attribute:height:imageBlock', converter, { priority: 'high' });
  };

  function converter(evt, data, conversionApi) {
    const { item } = data;
    const { consumable, writer } = conversionApi;

    if (!consumable.consume(item, evt.name) ) {
      return;
    }

    const viewElement = conversionApi.mapper.toViewElement(item);
    const imageInFigure = Array.from(viewElement.getChildren()).find(child => child.name === 'img');

    writer.setAttribute('height', data.attributeNewValue.replace('px', ''), imageInFigure || viewElement);
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc