| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- /**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- /* globals ClassicEditor, toWidget, toWidgetEditable, console, window, document */
- import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
- class InfoBox {
- constructor( editor ) {
- // Schema definition.
- editor.model.schema.register( 'infoBox', {
- allowWhere: '$block',
- allowContentOf: '$root',
- isObject: true,
- allowAttributes: [ 'infoBoxType' ]
- } );
- // Upcast converter.
- editor.conversion.for( 'upcast' )
- .add( dispatcher => dispatcher.on( 'element:div', upcastConverter ) );
- // The downcast conversion must be split as you need a widget in the editing pipeline.
- editor.conversion.for( 'editingDowncast' )
- .add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
- editor.conversion.for( 'dataDowncast' )
- .add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
- }
- }
- function upcastConverter( event, data, conversionApi ) {
- const viewInfoBox = data.viewItem;
- // Detect that a view element is an info box <div>.
- // Otherwise, it should be handled by another converter.
- if ( !viewInfoBox.hasClass( 'info-box' ) ) {
- return;
- }
- // Create the model structure.
- const modelElement = conversionApi.writer.createElement( 'infoBox', {
- infoBoxType: getTypeFromViewElement( viewInfoBox )
- } );
- // Try to safely insert the element into the model structure.
- // If `safeInsert()` returns `false`, the element cannot be safely inserted
- // into the content and the conversion process must stop.
- // This may happen if the data that you are converting has an incorrect structure
- // (e.g. it was copied from an external website).
- if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) {
- return;
- }
- // Mark the info box <div> as handled by this converter.
- conversionApi.consumable.consume( viewInfoBox, { name: true } );
- // Let us assume that the HTML structure is always the same.
- // Note: For full bulletproofing this converter, you should also check
- // whether these elements are the right ones.
- const viewInfoBoxTitle = viewInfoBox.getChild( 0 );
- const viewInfoBoxContent = viewInfoBox.getChild( 1 );
- // Mark info box inner elements (title and content <div>s) as handled by this converter.
- conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } );
- conversionApi.consumable.consume( viewInfoBoxContent, { name: true } );
- // Let the editor handle the children of <div class="info-box-content">.
- conversionApi.convertChildren( viewInfoBoxContent, modelElement );
- // Finally, update the conversion's modelRange and modelCursor.
- conversionApi.updateConversionResult( modelElement, data );
- }
- function editingDowncastConverter( event, data, conversionApi ) {
- let { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
- // Decorate view items as a widget and widget editable area.
- infoBox = toWidget( infoBox, conversionApi.writer, { label: 'info box widget' } );
- infoBoxContent = toWidgetEditable( infoBoxContent, conversionApi.writer );
- insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
- }
- function dataDowncastConverter( event, data, conversionApi ) {
- const { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
- insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
- }
- function createViewElements( data, conversionApi ) {
- const type = data.item.getAttribute( 'infoBoxType' );
- const infoBox = conversionApi.writer.createContainerElement( 'div', {
- class: `info-box info-box-${ type.toLowerCase() }`
- } );
- const infoBoxContent = conversionApi.writer.createEditableElement( 'div', {
- class: 'info-box-content'
- } );
- const infoBoxTitle = conversionApi.writer.createUIElement( 'div',
- { class: 'info-box-title' },
- function( domDocument ) {
- const domElement = this.toDomElement( domDocument );
- domElement.innerText = type;
- return domElement;
- } );
- return { infoBox, infoBoxContent, infoBoxTitle };
- }
- function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ) {
- conversionApi.consumable.consume( data.item, 'insert' );
- conversionApi.writer.insert(
- conversionApi.writer.createPositionAt( infoBox, 0 ),
- infoBoxTitle
- );
- conversionApi.writer.insert(
- conversionApi.writer.createPositionAt( infoBox, 1 ),
- infoBoxContent
- );
- // The default mapping between the model <infoBox> and its view representation.
- conversionApi.mapper.bindElements( data.item, infoBox );
- // However, since the model <infoBox> content needs to end up in the inner
- // <div class="info-box-content">, you need to bind one with another overriding
- // a part of the default binding.
- conversionApi.mapper.bindElements( data.item, infoBoxContent );
- conversionApi.writer.insert(
- conversionApi.mapper.toViewPosition( data.range.start ),
- infoBox
- );
- }
- ClassicEditor
- .create( document.querySelector( '#editor-custom-element-converter' ), {
- cloudServices: CS_CONFIG,
- extraPlugins: [ InfoBox ],
- image: {
- toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
- },
- table: {
- contentToolbar: [
- 'tableColumn',
- 'tableRow',
- 'mergeTableCells'
- ]
- },
- toolbar: {
- viewportTopOffset: window.getViewportTopOffsetConfig()
- }
- } )
- .then( editor => {
- window.editor = editor;
- } )
- .catch( err => {
- console.error( err.stack );
- } );
- function getTypeFromViewElement( viewElement ) {
- if ( viewElement.hasClass( 'info-box-info' ) ) {
- return 'Info';
- }
- if ( viewElement.hasClass( 'info-box-warning' ) ) {
- return 'Warning';
- }
- return 'None';
- }
|