8
0

imagecaptioningengine.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module image/imagecaptioning/imagecaptioningengine
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ModelTreeWalker from '@ckeditor/ckeditor5-engine/src/model/treewalker';
  10. import { insertElement } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
  11. import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
  12. import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
  13. import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
  14. import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
  15. import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
  16. import ViewMatcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
  17. import { isImage } from '../utils';
  18. export default class ImageCaptioningEngine extends Plugin {
  19. /**
  20. * @inheritDoc
  21. */
  22. init() {
  23. const editor = this.editor;
  24. const document = editor.document;
  25. const schema = document.schema;
  26. const data = editor.data;
  27. const editing = editor.editing;
  28. // Schema configuration.
  29. schema.registerItem( 'caption' );
  30. schema.allow( { name: '$inline', inside: 'caption' } );
  31. schema.allow( { name: 'caption', inside: 'image' } );
  32. // Add caption element to each image inserted without it.
  33. document.on( 'change', insertCaptionElement );
  34. // View to model converter for data pipeline.
  35. const matcher = new ViewMatcher( ( element ) => {
  36. const parent = element.parent;
  37. // Convert only captions for images.
  38. if ( element.name == 'figcaption' && parent && parent.name == 'figure' && parent.hasClass( 'image' ) ) {
  39. return { name: true };
  40. }
  41. return null;
  42. } );
  43. buildViewConverter()
  44. .for( data.viewToModel )
  45. .from( matcher )
  46. .toElement( 'caption' );
  47. // Model to view converter for editing pipeline.
  48. const insertConverter = insertElement( new ViewContainerElement( 'figcaption', { contenteditable: true } ) );
  49. editing.modelToView.on( 'insert:caption', ( evt, data, consumable, conversionApi ) => {
  50. const captionElement = data.item;
  51. if ( isImage( captionElement.parent ) ) {
  52. insertConverter( evt, data, consumable, conversionApi );
  53. }
  54. } );
  55. }
  56. }
  57. function insertCaptionElement( evt, changeType, data, batch ) {
  58. if ( changeType !== 'insert' ) {
  59. return;
  60. }
  61. const walker = new ModelTreeWalker( {
  62. boundaries: data.range,
  63. ignoreElementEnd: true
  64. } );
  65. for ( let value of walker ) {
  66. const item = value.item;
  67. if ( value.type == 'elementStart' && item.name == 'image' && !hasCaption( item ) ) {
  68. // Using batch of insertion.
  69. batch.document.enqueueChanges( () => {
  70. batch.insert( ModelPosition.createAt( item, 'end' ), new ModelElement( 'caption' ) );
  71. } );
  72. }
  73. }
  74. }
  75. function hasCaption( image ) {
  76. for ( let node of image.getChildren() ) {
  77. if ( node instanceof ModelElement && node.name == 'caption' ) {
  78. return true;
  79. }
  80. }
  81. return false;
  82. }