extending-content-custom-element-converter.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals ClassicEditor, toWidget, toWidgetEditable, console, window, document */
  6. import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
  7. class InfoBox {
  8. constructor( editor ) {
  9. // Schema definition
  10. editor.model.schema.register( 'infoBox', {
  11. allowWhere: '$block',
  12. allowContentOf: '$root',
  13. isObject: true,
  14. allowAttributes: [ 'infoBoxType' ]
  15. } );
  16. // Upcast converter.
  17. editor.conversion.for( 'upcast' )
  18. .add( dispatcher => dispatcher.on( 'element:div', upcastConverter ) );
  19. // The downcast conversion must be split as we need a widget in the editing pipeline.
  20. editor.conversion.for( 'editingDowncast' )
  21. .add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
  22. editor.conversion.for( 'dataDowncast' )
  23. .add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
  24. }
  25. }
  26. function upcastConverter( event, data, conversionApi ) {
  27. const viewInfoBox = data.viewItem;
  28. // Detect that view element is an info-box div.
  29. // Otherwise, it should be handled by another converter.
  30. if ( !viewInfoBox.hasClass( 'info-box' ) ) {
  31. return;
  32. }
  33. // Create a model structure.
  34. const modelElement = conversionApi.writer.createElement( 'infoBox', {
  35. infoBoxType: getTypeFromViewElement( viewInfoBox )
  36. } );
  37. // Try to safely insert element - if it returns false the element can't be safely inserted
  38. // into the content, and the conversion process must stop.
  39. if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) {
  40. return;
  41. }
  42. // Mark info-box div as handled by this converter.
  43. conversionApi.consumable.consume( viewInfoBox, { name: true } );
  44. // Let's assume that the HTML structure is always the same.
  45. const viewInfoBoxTitle = viewInfoBox.getChild( 0 );
  46. const viewInfoBoxContent = viewInfoBox.getChild( 1 );
  47. // Mark info-box inner elements as handled by this converter.
  48. conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } );
  49. conversionApi.consumable.consume( viewInfoBoxContent, { name: true } );
  50. // Let the editor handle children of the info-box content conversion.
  51. conversionApi.convertChildren( viewInfoBoxContent, modelElement );
  52. // Conversion requires updating result data structure properly.
  53. conversionApi.updateConversionResult( modelElement, data );
  54. }
  55. function editingDowncastConverter( event, data, conversionApi ) {
  56. let { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
  57. // Decorate view items as widgets.
  58. infoBox = toWidget( infoBox, conversionApi.writer, { label: 'simple box widget' } );
  59. infoBoxContent = toWidgetEditable( infoBoxContent, conversionApi.writer );
  60. insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
  61. }
  62. function dataDowncastConverter( event, data, conversionApi ) {
  63. const { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
  64. insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
  65. }
  66. function createViewElements( data, conversionApi ) {
  67. const type = data.item.getAttribute( 'infoBoxType' );
  68. const infoBox = conversionApi.writer.createContainerElement( 'div', {
  69. class: `info-box info-box-${ type.toLowerCase() }`
  70. } );
  71. const infoBoxContent = conversionApi.writer.createEditableElement( 'div', {
  72. class: 'info-box-content'
  73. } );
  74. const infoBoxTitle = conversionApi.writer.createUIElement( 'div',
  75. { class: 'info-box-title' },
  76. function( domDocument ) {
  77. const domElement = this.toDomElement( domDocument );
  78. domElement.innerText = type;
  79. return domElement;
  80. } );
  81. return { infoBox, infoBoxContent, infoBoxTitle };
  82. }
  83. function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ) {
  84. conversionApi.consumable.consume( data.item, 'insert' );
  85. conversionApi.writer.insert(
  86. conversionApi.writer.createPositionAt( infoBox, 0 ),
  87. infoBoxTitle
  88. );
  89. conversionApi.writer.insert(
  90. conversionApi.writer.createPositionAt( infoBox, 1 ),
  91. infoBoxContent
  92. );
  93. conversionApi.mapper.bindElements( data.item, infoBox );
  94. conversionApi.mapper.bindElements( data.item, infoBoxContent );
  95. conversionApi.writer.insert(
  96. conversionApi.mapper.toViewPosition( data.range.start ),
  97. infoBox
  98. );
  99. }
  100. ClassicEditor
  101. .create( document.querySelector( '#editor-custom-element-converter' ), {
  102. cloudServices: CS_CONFIG,
  103. extraPlugins: [ InfoBox ],
  104. image: {
  105. toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
  106. },
  107. table: {
  108. contentToolbar: [
  109. 'tableColumn',
  110. 'tableRow',
  111. 'mergeTableCells'
  112. ]
  113. },
  114. toolbar: {
  115. viewportTopOffset: window.getViewportTopOffsetConfig()
  116. }
  117. } )
  118. .then( editor => {
  119. window.editor = editor;
  120. } )
  121. .catch( err => {
  122. console.error( err.stack );
  123. } );
  124. function getTypeFromViewElement( viewElement ) {
  125. if ( viewElement.hasClass( 'info-box-info' ) ) {
  126. return 'Info';
  127. }
  128. if ( viewElement.hasClass( 'info-box-warning' ) ) {
  129. return 'Warning';
  130. }
  131. return 'None';
  132. }