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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. ClassicEditor
  8. .create( document.querySelector( '#editor-custom-element-converter' ), {
  9. cloudServices: CS_CONFIG,
  10. extraPlugins: [ function( editor ) {
  11. editor.model.schema.register( 'infoBox', {
  12. allowWhere: '$block',
  13. allowContentOf: '$root',
  14. isObject: true,
  15. allowAttributes: [ 'infoBoxType' ]
  16. } );
  17. editor.conversion.for( 'upcast' ).add( dispatcher => dispatcher.on( 'element:div', ( event, data, conversionApi ) => {
  18. const viewInfoBox = data.viewItem;
  19. // . Detect that converted div is the one we need to convert.
  20. // . Create model structure.
  21. const modelElement = conversionApi.writer.createElement( 'infoBox', {
  22. infoBoxType: getTypeFromViewElement( viewInfoBox )
  23. } );
  24. // . Try to safely insert element - if it returns false the element can't be safely inserted into
  25. // the content and the conversion process must stop.
  26. if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) {
  27. return;
  28. }
  29. // . Mark info-box div as handled by this converter.
  30. if ( !conversionApi.consumable.consume( viewInfoBox, { name: true } ) ) {
  31. return; // If already handled by another converter.
  32. }
  33. // For the sake of simplicity we assume that HTML structure is always the same.
  34. // If you can't control the order of view elements a more sophisticated search might be needed.
  35. const viewInfoBoxTitle = viewInfoBox.getChild( 0 );
  36. const viewInfoBoxContent = viewInfoBox.getChild( 1 );
  37. conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } );
  38. conversionApi.consumable.consume( viewInfoBoxContent, { name: true } );
  39. // . Make editor handle children conversion.
  40. conversionApi.convertChildren( viewInfoBoxContent, modelElement );
  41. // . Conversion requires updating data structure properly.
  42. conversionApi.updateConversionResult( modelElement, data );
  43. } ) );
  44. editor.conversion.for( 'downcast' ).add(
  45. dispatcher => dispatcher.on( 'insert:infoBox', ( event, data, conversionApi ) => {
  46. const modelElement = data.item;
  47. const viewWriter = conversionApi.writer;
  48. const type = modelElement.getAttribute( 'infoBoxType' );
  49. const infoBox = toWidget(
  50. viewWriter.createContainerElement( 'div', { class: `info-box info-box-${ type }` } ),
  51. viewWriter,
  52. { label: 'simple box widget' }
  53. );
  54. const infoBoxTitle = viewWriter.createUIElement( 'div', { class: 'info-box-title' }, function( domDocument ) {
  55. const domElement = this.toDomElement( domDocument );
  56. domElement.innerText = type;
  57. return domElement;
  58. } );
  59. const infoBoxContent = toWidgetEditable(
  60. viewWriter.createEditableElement( 'div', { class: 'info-box-content' } ),
  61. viewWriter
  62. );
  63. conversionApi.consumable.consume( modelElement, 'insert' );
  64. const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
  65. viewWriter.insert( viewWriter.createPositionAt( infoBox, 0 ), infoBoxTitle );
  66. viewWriter.insert( viewWriter.createPositionAt( infoBox, 1 ), infoBoxContent );
  67. // Bind both...
  68. conversionApi.mapper.bindElements( modelElement, infoBox );
  69. conversionApi.mapper.bindElements( modelElement, infoBoxContent );
  70. conversionApi.writer.insert( viewPosition, infoBox );
  71. } )
  72. );
  73. } ],
  74. toolbar: [
  75. 'heading',
  76. '|',
  77. 'bold',
  78. 'italic',
  79. 'link',
  80. 'bulletedList',
  81. 'numberedList',
  82. '|',
  83. 'outdent',
  84. 'indent',
  85. '|',
  86. 'blockQuote',
  87. 'insertTable',
  88. 'mediaEmbed',
  89. 'undo',
  90. 'redo'
  91. ],
  92. image: {
  93. toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
  94. },
  95. table: {
  96. contentToolbar: [
  97. 'tableColumn',
  98. 'tableRow',
  99. 'mergeTableCells'
  100. ]
  101. }
  102. // toolbar: {
  103. // viewportTopOffset: window.getViewportTopOffsetConfig()
  104. // }
  105. } )
  106. .then( editor => {
  107. window.editor = editor;
  108. } )
  109. .catch( err => {
  110. console.error( err.stack );
  111. } );
  112. function getTypeFromViewElement( viewElement ) {
  113. const classNames = [ ...viewElement.getClassNames() ];
  114. if ( classNames.includes( 'info-box-info' ) ) {
  115. return 'info';
  116. }
  117. if ( classNames.includes( 'info-box-warning' ) ) {
  118. return 'warning';
  119. }
  120. return 'none';
  121. }