From a7a0dfde792a5bccf41be8b89dbb2d056eae1c66 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 4 Aug 2021 20:37:28 +0200 Subject: [PATCH 001/140] The elementToStructure downcast helper PoC extracted from other PoC branch. --- .../src/conversion/downcastdispatcher.js | 208 ++------------ .../src/conversion/downcasthelpers.js | 269 +++++++++++++++++- .../ckeditor5-engine/src/conversion/mapper.js | 35 ++- .../tests/conversion/downcastdispatcher.js | 4 +- .../tests/conversion/downcasthelpers.js | 267 +++++++++++------ .../tests/manual/slotconversion.js | 45 +-- 6 files changed, 504 insertions(+), 324 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 54923ce26a5..77f995cdb4e 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -9,7 +9,6 @@ import Consumable from './modelconsumable'; import Range from '../model/range'; -import Position, { getNodeAfterPosition, getTextNodeAtPosition } from '../model/position'; import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; @@ -123,14 +122,6 @@ export default class DowncastDispatcher { * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi} */ this.conversionApi = Object.assign( { dispatcher: this }, conversionApi ); - - /** - * Maps conversion event names that will trigger element reconversion for a given element name. - * - * @type {Map} - * @private - */ - this._reconversionEventsMapping = new Map(); } /** @@ -146,7 +137,13 @@ export default class DowncastDispatcher { this.convertMarkerRemove( change.name, change.range, writer ); } - const changes = this._mapChangesWithAutomaticReconversion( differ ); + const reduceChangesData = { + changes: differ.getChanges() + }; + + this.fire( 'reduceChanges', reduceChangesData ); + + const changes = reduceChangesData.changes; // Convert changes that happened on model tree. for ( const entry of changes ) { @@ -155,7 +152,7 @@ export default class DowncastDispatcher { } else if ( entry.type === 'remove' ) { this.convertRemove( entry.position, entry.length, entry.name, writer ); } else if ( entry.type === 'reconvert' ) { - this.reconvertElement( entry.element, writer ); + this.reconvertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); } else { // Defaults to 'attribute' change. this.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, writer ); @@ -173,6 +170,8 @@ export default class DowncastDispatcher { for ( const change of differ.getMarkersToAdd() ) { this.convertMarkerAdd( change.name, change.range, writer ); } + + this.conversionApi.mapper.flushTemporaryMappings(); } /** @@ -192,10 +191,7 @@ export default class DowncastDispatcher { // Create a list of things that can be consumed, consisting of nodes and their attributes. this.conversionApi.consumable = this._createInsertConsumable( range ); - // Fire a separate insert event for each node and text fragment contained in the range. - for ( const data of Array.from( range ).map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( data ); - } + this._convertInsert( range ); this._clearConversionApi(); } @@ -253,6 +249,8 @@ export default class DowncastDispatcher { } /** + * TODO + * * Starts the reconversion of an element. It will: * * * Fire an {@link #event:insert `insert` event} for the element to reconvert. @@ -269,53 +267,20 @@ export default class DowncastDispatcher { * @param {module:engine/model/element~Element} element The element to be reconverted. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ - reconvertElement( element, writer ) { - const elementRange = Range._createOn( element ); - + reconvertInsert( range, writer ) { this.conversionApi.writer = writer; // Create a list of things that can be consumed, consisting of nodes and their attributes. - this.conversionApi.consumable = this._createInsertConsumable( elementRange ); - - const mapper = this.conversionApi.mapper; - const currentView = mapper.toViewElement( element ); - - // Remove the old view but do not remove mapper mappings - those will be used to revive existing elements. - writer.remove( currentView ); - - // Convert the element - without converting children. - this._convertInsertWithAttributes( { - item: element, - range: elementRange - } ); - - const convertedViewElement = mapper.toViewElement( element ); - - // Iterate over children of reconverted element in order to... - for ( const value of Range._createIn( element ) ) { - const { item } = value; - - const view = elementOrTextProxyToView( item, mapper ); - - // ...either bring back previously converted view... - if ( view ) { - // Do not move views that are already in converted element - those might be created by the main element converter in case - // when main element converts also its direct children. - if ( view.root !== convertedViewElement.root ) { - writer.move( - writer.createRangeOn( view ), - mapper.toViewPosition( Position._createBefore( item ) ) - ); - } - } - // ... or by converting newly inserted elements. - else { - this._convertInsertWithAttributes( walkerValueToEventData( value ) ); - } - } + this.conversionApi.consumable = this._createInsertConsumable( range ); - // After reconversion is done we can unbind the old view. - mapper.unbindViewElement( currentView ); + // Convert the elements - without converting children. + // Fire a separate insert event for each node and text fragment contained in the range. + for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { + this._convertInsertWithAttributes( { + ...data, + reconversion: true + } ); + } this._clearConversionApi(); } @@ -462,22 +427,15 @@ export default class DowncastDispatcher { } /** - * Maps the model element "insert" reconversion for given event names. The event names must be fully specified: - * - * * For "attribute" change event, it should include the main element name, i.e: `'attribute:attributeName:elementName'`. - * * For child node change events, these should use the child event name as well, i.e: - * * For adding a node: `'insert:childElementName'`. - * * For removing a node: `'remove:childElementName'`. - * - * **Note**: This method should not be used directly. The reconversion is defined by the `triggerBy()` configuration of the - * `elementToElement()` conversion helper. + * TODO maybe this should be sth like convertNestedInsert to indicate that it's inside the current outer conversion block? * * @protected - * @param {String} modelName The name of the main model element for which the events will trigger the reconversion. - * @param {String} eventName The name of an event that would trigger conversion for a given model element. */ - _mapReconversionTriggerEvent( modelName, eventName ) { - this._reconversionEventsMapping.set( eventName, modelName ); + _convertInsert( range ) { + // Fire a separate insert event for each node and text fragment contained in the range. + for ( const data of Array.from( range ).map( walkerValueToEventData ) ) { + this._convertInsertWithAttributes( data ); + } } /** @@ -597,103 +555,6 @@ export default class DowncastDispatcher { } } - /** - * Returns differ changes together with added "reconvert" type changes for {@link #reconvertElement}. These are defined by - * a the `triggerBy()` configuration for the - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} conversion helper. - * - * This method will remove every mapped insert or remove change with a single "reconvert" change. - * - * For instance: Having a `triggerBy()` configuration defined for the `` element that issues this element reconversion on - * `foo` and `bar` attributes change, and a set of changes for this element: - * - * const differChanges = [ - * { type: 'attribute', attributeKey: 'foo', ... }, - * { type: 'attribute', attributeKey: 'bar', ... }, - * { type: 'attribute', attributeKey: 'baz', ... } - * ]; - * - * This method will return: - * - * const updatedChanges = [ - * { type: 'reconvert', element: complexElementInstance }, - * { type: 'attribute', attributeKey: 'baz', ... } - * ]; - * - * In the example above, the `'baz'` attribute change will fire an {@link #event:attribute attribute event} - * - * @param {module:engine/model/differ~Differ} differ The differ object with buffered changes. - * @returns {Array.} Updated set of changes. - * @private - */ - _mapChangesWithAutomaticReconversion( differ ) { - const itemsToReconvert = new Set(); - const updated = []; - - for ( const entry of differ.getChanges() ) { - const position = entry.position || entry.range.start; - // Cached parent - just in case. See https://github.com/ckeditor/ckeditor5/issues/6579. - const positionParent = position.parent; - const textNode = getTextNodeAtPosition( position, positionParent ); - - // Reconversion is done only on elements so skip text changes. - if ( textNode ) { - updated.push( entry ); - - continue; - } - - const element = entry.type === 'attribute' ? getNodeAfterPosition( position, positionParent, null ) : positionParent; - - // Case of text node set directly in root. For now used only in tests but can be possible when enabled in paragraph-like roots. - // See: https://github.com/ckeditor/ckeditor5/issues/762. - if ( element.is( '$text' ) ) { - updated.push( entry ); - - continue; - } - - let eventName; - - if ( entry.type === 'attribute' ) { - eventName = `attribute:${ entry.attributeKey }:${ element.name }`; - } else { - eventName = `${ entry.type }:${ entry.name }`; - } - - if ( this._isReconvertTriggerEvent( eventName, element.name ) ) { - if ( itemsToReconvert.has( element ) ) { - // Element is already reconverted, so skip this change. - continue; - } - - itemsToReconvert.add( element ); - - // Add special "reconvert" change. - updated.push( { type: 'reconvert', element } ); - } else { - updated.push( entry ); - } - } - - return updated; - } - - /** - * Checks if the resulting change should trigger element reconversion. - * - * These are defined by a `triggerBy()` configuration for the - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} conversion helper. - * - * @private - * @param {String} eventName The event name to check. - * @param {String} elementName The element name to check. - * @returns {Boolean} - */ - _isReconvertTriggerEvent( eventName, elementName ) { - return this._reconversionEventsMapping.get( eventName ) === elementName; - } - /** * Fired for inserted nodes. * @@ -857,17 +718,6 @@ function walkerValueToEventData( value ) { }; } -function elementOrTextProxyToView( item, mapper ) { - if ( item.is( 'textProxy' ) ) { - const mappedPosition = mapper.toViewPosition( Position._createBefore( item ) ); - const positionParent = mappedPosition.parent; - - return positionParent.is( '$text' ) ? positionParent : null; - } - - return mapper.toViewElement( item ); -} - /** * Conversion interface that is registered for given {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} * and is passed as one of parameters when {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher dispatcher} diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 229622749af..27c9a1d5f55 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -12,6 +12,7 @@ import ModelRange from '../model/range'; import ModelSelection from '../model/selection'; import ModelElement from '../model/element'; +import ModelPosition from '../model/position'; import ViewAttributeElement from '../view/attributeelement'; import DocumentSelection from '../model/documentselection'; @@ -96,6 +97,13 @@ export default class DowncastHelpers extends ConversionHelpers { return this.add( downcastElementToElement( config ) ); } + /** + * TODO + */ + elementToStructure( config ) { + return this.add( downcastElementToStructure( config ) ); + } + /** * Model attribute to view element conversion helper. * @@ -565,7 +573,7 @@ export function remove() { // After the range is removed, unbind all view elements from the model. // Range inside view document fragment is used to unbind deeply. for ( const child of conversionApi.writer.createRangeIn( removed ).getItems() ) { - conversionApi.mapper.unbindViewElement( child ); + conversionApi.mapper.unbindViewElement( child, false ); } }; } @@ -824,6 +832,175 @@ export function insertElement( elementCreator ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); + + // Fill with nested view nodes. + for ( const modelChildNode of data.item.getChildren() ) { + const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); + + if ( data.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { + conversionApi.writer.move( + conversionApi.writer.createRangeOn( viewChildNode ), + conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + // conversionApi.mapper.bindElements( modelChildNode, viewChildNode ); + } else { + // TODO this should be exposed by conversionApi? + // Note that this is not creating another consumable, it's using the current one. + conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); + } + } + }; +} + +/** + * TODO + */ +function insertStructure( elementCreator ) { + return ( evt, data, conversionApi ) => { + const { writer, mapper, consumable } = conversionApi; + + // Scan for elements and it's current mapped view elements. + const elements = Array.from( data.range.getItems( { shallow: true } ) ); + // const consumables = data.consumables || elements.map( element => [ element, 'insert' ] ); + const consumables = []; // TODO + + // Verify if all consumables are available to be consumed. + for ( const [ item, eventName ] of consumables ) { + if ( !consumable.test( item, eventName ) ) { + return; + } + } + + const slotsMap = new Map(); + + // View creation. + const viewElement = elementCreator( data.range, { + ...conversionApi, + slotFor( element, mode = 'children' ) { + const slot = writer.createContainerElement( '$slot' ); + + if ( slotsMap.has( element ) ) { + slotsMap.get( element ).push( { slot, mode } ); + } else { + slotsMap.set( element, [ { slot, mode } ] ); + } + + return slot; + } + } ); + + // Insert the new structure if any was created. Otherwise it's removed. + if ( !viewElement ) { + return; + } + + // Consume consumables. + for ( const [ item, eventName ] of consumables ) { + if ( !consumable.consume( item, eventName ) ) { + return; + } + } + + const viewPosition = mapper.toViewPosition( data.range.start ); + + // TODO make sure that provided viewElement has no bindings (else there could be an infinite loop). + + mapper.bindElements( elements[ 0 ], viewElement ); + writer.insert( viewPosition, viewElement ); + + // mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); + + // Fill slots with nested view nodes. + for ( const [ element, elementSlots ] of slotsMap.entries() ) { + for ( const { slot, mode } of elementSlots ) { + // TODO Make sure that slots are created for the elements themself or direct descendants and not other elements. + + if ( mode == 'children' ) { + // Bind slot with element so children will be down-casted into it. + // TODO use temporary modelToViewPosition mapping + mapper.bindElements( element, slot ); + + for ( const modelChildNode of element.getChildren() ) { + const viewChildNode = elementOrTextProxyToView( modelChildNode, mapper ); + + if ( data.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { // TODO ??? + writer.move( + writer.createRangeOn( viewChildNode ), + mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + // console.log( 'reused old view for', modelChildNode ); + // mapper.bindElements( modelChildNode, viewChildNode ); + } else { + // TODO this should be exposed by conversionApi? + // Note that this is not creating another consumable, it's using the current one. + conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); + } + } + + writer.move( writer.createRangeIn( slot ), writer.createPositionBefore( slot ) ); + mapper.unbindViewElement( slot ); + writer.remove( slot ); + + // Rebind the original view element to the correct model element. + mapper.bindElements( element, viewElement ); + // } else if ( mode == 'self' ) { + // // const currentViewElement = currentMapping.get( element ); + // + // // Bind slot parents to the element so they could be found while reconverting. + // // TODO probably all parents that have no mapping yet (so excluding main viewElement) + // mapper.bindElements( element, slot.parent ); + // + // // if ( range.reconversion && currentViewElement && currentViewElement.root != viewElement.root ) { // TODO ??? + // // writer.move( + // // writer.createRangeOn( currentViewElement ), + // // mapper.toViewPosition( ModelPosition._createBefore( element ) ) + // // ); + // // console.log( 'reused old view for element at', + // ModelPosition._createBefore( element ).path, '->', currentViewElement ); + // // + // // // Rebind the original view element to the correct model element. + // // mapper.bindElements( element, currentViewElement ); + // // } else { + // // TODO this should be exposed by conversionApi? + // // Note that this is not creating another consumable, it's using the current one. + // conversionApi.dispatcher._convertInsert( ModelRange._createOn( element ) ); + // // } + // + // writer.remove( slot ); + } else { + // TODO callback check (for example tbody vs thead) + } + } + } + + // mapper.off( 'modelToViewPosition', toViewPositionMapping ); + + // // After reconversion is done we can unbind the old view. + // for ( const currentView of currentViewElements ) { + // if ( currentView.root != viewElement.root ) { + // mapper.unbindViewElement( currentView ); + // } + // } + + // function toViewPositionMapping( evt, data ) { + // if ( data.isPhantom || data.viewPosition ) { + // return; + // } + // + // const elementSlots = slotsMap.get( data.modelPosition.nodeAfter ); + // + // if ( !elementSlots ) { + // return; + // } + // + // // TODO conditional slots? + // + // if ( elementSlots[ 0 ].mode == 'self' ) { + // data.viewPosition = writer.createPositionBefore( elementSlots[ 0 ].slot ); + // } + // } + + return viewElement; }; } @@ -1381,18 +1558,25 @@ function downcastElementToElement( config ) { dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); if ( config.triggerBy ) { - if ( config.triggerBy.attributes ) { - for ( const attributeKey of config.triggerBy.attributes ) { - dispatcher._mapReconversionTriggerEvent( config.model, `attribute:${ attributeKey }:${ config.model }` ); - } - } + dispatcher.on( 'reduceChanges', createTriggerByHandler( config ) ); + } + }; +} - if ( config.triggerBy.children ) { - for ( const childName of config.triggerBy.children ) { - dispatcher._mapReconversionTriggerEvent( config.model, `insert:${ childName }` ); - dispatcher._mapReconversionTriggerEvent( config.model, `remove:${ childName }` ); - } - } +// TODO +function downcastElementToStructure( config ) { + config = cloneDeep( config ); + + const elementCreator = normalizeToElementConfig( config.view, 'container' ); + + // TODO The elementCreator expects model element here but insertStructure operates on ranges. + config.view = ( range, ...rest ) => elementCreator( range.start.nodeAfter, ...rest ); + + return dispatcher => { + dispatcher.on( 'insert:' + config.model, insertStructure( config.view ), { priority: config.converterPriority || 'normal' } ); + + if ( config.triggerBy ) { + dispatcher.on( 'reduceChanges', createTriggerByHandler( config ) ); } }; } @@ -1670,6 +1854,67 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) { return descriptor; } +// TODO +function createTriggerByHandler( config ) { + return ( evt, data ) => { + const reducedChanges = []; + const reconvertedElements = new Set(); + + for ( const diffItem of data.changes ) { + // For attribute use node affected by the change. + // For insert or remove use parent element because we need to check if it's added/removed child. + const element = diffItem.position ? diffItem.position.parent : diffItem.range.start.nodeAfter; + + if ( element.name != config.model ) { + return; + } + + if ( diffItem.type == 'attribute' ) { + if ( !config.triggerBy.attributes || !config.triggerBy.attributes.includes( diffItem.attributeKey ) ) { + return; + } + } else { + if ( !config.triggerBy.children || !config.triggerBy.children.includes( diffItem.name ) ) { + return; + } + } + + // If it's already marked for reconversion, so skip this change. + if ( reconvertedElements.has( element ) ) { + continue; + } + + reconvertedElements.add( element ); + + const position = ModelPosition._createBefore( element ); + + reducedChanges.push( { + type: 'remove', + position, + length: 1 + }, { + type: 'reconvert', // TODO is this needed? what about element renaming ( paragraph => heading1 ) + position, + length: 1 + } ); + } + + data.changes = reducedChanges; + }; +} + +// TODO +function elementOrTextProxyToView( item, mapper ) { + if ( item.is( '$text' ) ) { + const viewPosition = mapper.toViewPosition( ModelPosition._createBefore( item ) ); + const viewPositionParent = viewPosition.parent; + + return viewPositionParent.is( '$text' ) ? viewPositionParent : null; + } + + return mapper.toViewElement( item ); +} + /** * An object describing how the marker highlight should be represented in the view. * diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 1c9fed36963..923d2bc0510 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -88,6 +88,12 @@ export default class Mapper { */ this._elementToMarkerNames = new Map(); + /** + * TODO + * @private + */ + this._removedViewElements = new Set(); + /** * Stores marker names of markers which has changed due to unbinding a view element (so it is assumed that the view element * has been removed, moved or renamed). @@ -147,19 +153,23 @@ export default class Mapper { * * @param {module:engine/view/element~Element} viewElement View element to unbind. */ - unbindViewElement( viewElement ) { + unbindViewElement( viewElement, immediate = true ) { const modelElement = this.toModelElement( viewElement ); - this._viewToModelMapping.delete( viewElement ); - if ( this._elementToMarkerNames.has( viewElement ) ) { for ( const markerName of this._elementToMarkerNames.get( viewElement ) ) { this._unboundMarkerNames.add( markerName ); } } - if ( this._modelToViewMapping.get( modelElement ) == viewElement ) { - this._modelToViewMapping.delete( modelElement ); + if ( immediate ) { + this._viewToModelMapping.delete( viewElement ); + + if ( this._modelToViewMapping.get( modelElement ) == viewElement ) { + this._modelToViewMapping.delete( modelElement ); + } + } else { + this._removedViewElements.add( [ viewElement, viewElement.root ] ); } } @@ -244,6 +254,20 @@ export default class Mapper { return markerNames; } + /** + * TODO + */ + flushTemporaryMappings() { + for ( const [ viewElement, root ] of this._removedViewElements ) { + // Unbind it only if it wasn't re-attached to some root or document fragment. + if ( viewElement.root == root ) { + this.unbindViewElement( viewElement, true ); + } + } + + this._removedViewElements = new Set(); + } + /** * Removes all model to view and view to model bindings. */ @@ -253,6 +277,7 @@ export default class Mapper { this._markerNameToElements = new Map(); this._elementToMarkerNames = new Map(); this._unboundMarkerNames = new Set(); + this._removedViewElements = new Set(); } /** diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 422eed0a7db..832911952bd 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -80,7 +80,7 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it( 'should call convertAttribute for attribute change', () => { + it.skip( 'should call convertAttribute for attribute change', () => { sinon.stub( dispatcher, 'convertAttribute' ); sinon.stub( dispatcher, '_mapChangesWithAutomaticReconversion' ).callsFake( differ => differ.getChanges() ); @@ -101,7 +101,7 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it( 'should handle multiple changes', () => { + it.skip( 'should handle multiple changes', () => { sinon.stub( dispatcher, 'convertInsert' ); sinon.stub( dispatcher, 'convertRemove' ); sinon.stub( dispatcher, 'convertAttribute' ); diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index ce3b175014a..10959114a77 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -430,7 +430,7 @@ describe( 'DowncastHelpers', () => { it( 'should convert on adding a child (in the middle)', () => { setModelData( model, '' + - 'foobar' + + 'foobar' + '' ); const [ viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); @@ -496,6 +496,85 @@ describe( 'DowncastHelpers', () => { expect( paraAfter, 'para' ).to.equal( paraBefore ); expect( textAfter, 'text' ).to.equal( textBefore ); } ); + + // https://github.com/ckeditor/ckeditor5/issues/9641 + it( 'should convert on multiple similar child hooks', () => { + model.schema.register( 'simpleBlock2', { + allowIn: '$root', + allowChildren: 'paragraph' + } ); + downcastHelpers.elementToElement( { + model: 'simpleBlock2', + view: { name: 'div', classes: 'second' }, + triggerBy: { + children: [ 'paragraph' ] + } + } ); + + setModelData( model, + 'foo' + + 'bar' + ); + + const [ viewBefore0, paraBefore0, textBefore0 ] = getNodes( 0 ); + const [ viewBefore1, paraBefore1, textBefore1 ] = getNodes( 1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'abc' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter0, paraAfter0, textAfter0 ] = getNodes( 0 ); + const [ viewAfter1, paraAfter1, textAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( '123' ); + + writer.insert( paragraph, modelRoot.getChild( 1 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfterAfter0, paraAfterAfter0, textAfterAfter0 ] = getNodes( 0 ); + const [ viewAfterAfter1, paraAfterAfter1, textAfterAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

123

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + expect( viewAfterAfter0, 'simpleBlock' ).to.equal( viewAfter0 ); + expect( paraAfterAfter0, 'para' ).to.equal( paraAfter0 ); + expect( textAfterAfter0, 'text' ).to.equal( textAfter0 ); + + expect( viewAfterAfter1, 'simpleBlock' ).to.not.equal( viewAfter1 ); + expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); + expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); + } ); } ); describe( 'with complex view structure - no children allowed', () => { @@ -707,15 +786,16 @@ describe( 'DowncastHelpers', () => { } ); } ); + // TODO those tests are for elementToStructure describe( 'with complex view structure (slot conversion)', () => { beforeEach( () => { model.schema.register( 'complex', { allowIn: '$root', allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] } ); - downcastHelpers.elementToElement( { + downcastHelpers.elementToStructure( { model: 'complex', - view: ( modelElement, { writer, mapper } ) => { + view: ( modelElement, { writer, mapper, slotFor } ) => { const classForMain = !!modelElement.getAttribute( 'classForMain' ); const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); @@ -735,12 +815,14 @@ describe( 'DowncastHelpers', () => { writer.insert( writer.createPositionAt( outer, 'end' ), inner ); mapper.bindElements( modelElement, inner ); - for ( const slot of modelElement.getChildren() ) { - const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); + writer.insert( writer.createPositionAt( inner, 0 ), slotFor( modelElement, 'children' ) ); - writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); - mapper.bindElements( slot, viewSlot ); - } + // for ( const slot of modelElement.getChildren() ) { + // const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); + // + // writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); + // mapper.bindElements( slot, viewSlot ); + // } return outer; }, @@ -750,6 +832,11 @@ describe( 'DowncastHelpers', () => { } } ); + downcastHelpers.elementToElement( { + model: 'slot', + view: { name: 'div', classes: 'slot' } + } ); + model.schema.register( 'slot', { allowIn: 'complex' } ); @@ -802,16 +889,16 @@ describe( 'DowncastHelpers', () => { it( 'should convert element with slots', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '
' + '
' ); } ); @@ -819,8 +906,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on adding slot', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -829,11 +916,11 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + '
' ); } ); @@ -841,8 +928,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on removing slot', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -851,9 +938,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

bar

' + + '
' + '
' ); } ); @@ -861,8 +948,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on multiple triggers (remove + insert)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -873,9 +960,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + '
' + - '

bar

' + - '

baz

' + - '
' + + '

bar

' + + '

baz

' + + '
' + '' ); } ); @@ -883,8 +970,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on multiple triggers (remove + attribute)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -894,9 +981,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

bar

' + + '
' + '
' ); } ); @@ -904,8 +991,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on multiple triggers (insert + attribute)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -915,11 +1002,11 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + '
' ); } ); @@ -948,8 +1035,8 @@ describe( 'DowncastHelpers', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); const otherView = viewRoot.getChild( 0 ); @@ -960,9 +1047,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '

foo

' + - '

bar

' + - '

baz

' + + '

foo

' + + '

bar

' + + '

baz

' + '
' ); const otherViewAfter = viewRoot.getChild( 0 ); @@ -973,8 +1060,8 @@ describe( 'DowncastHelpers', () => { describe( 'memoization', () => { it( 'should create new element on re-converting element', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); @@ -991,8 +1078,8 @@ describe( 'DowncastHelpers', () => { it( 'should not re-create slot\'s child elements on re-converting main element (attribute changed)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); @@ -1009,8 +1096,8 @@ describe( 'DowncastHelpers', () => { slotTwoAfter, paraTwoAfter, textNodeTwoAfter ] = getNodes(); expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.not.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.not.equal( slotTwo ); + expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); + expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); @@ -1019,8 +1106,8 @@ describe( 'DowncastHelpers', () => { it( 'should not re-create slot\'s child elements on re-converting main element (slot added)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); @@ -1043,8 +1130,8 @@ describe( 'DowncastHelpers', () => { ] = getNodes(); expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.not.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.not.equal( slotTwo ); + expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); + expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); @@ -1167,16 +1254,16 @@ describe( 'DowncastHelpers', () => { it( 'should convert element with slots', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '
' + '
' ); } ); @@ -1184,8 +1271,8 @@ describe( 'DowncastHelpers', () => { it( 'should not convert element on adding slot', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -1198,11 +1285,11 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + '
' ); } ); @@ -1210,8 +1297,8 @@ describe( 'DowncastHelpers', () => { it( 'should not convert element on removing slot', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -1220,9 +1307,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

bar

' + + '
' + '
' ); } ); @@ -1230,8 +1317,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on a trigger and block atomic converters (remove + attribute)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -1241,9 +1328,9 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

bar

' + + '
' + '
' ); } ); @@ -1251,8 +1338,8 @@ describe( 'DowncastHelpers', () => { it( 'should convert element on a trigger and block atomic converters (insert + attribute)', () => { setModelData( model, '' + - 'foo' + - 'bar' + + 'foo' + + 'bar' + '' ); model.change( writer => { @@ -1262,11 +1349,11 @@ describe( 'DowncastHelpers', () => { expectResult( '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + '
' ); } ); @@ -1290,8 +1377,8 @@ describe( 'DowncastHelpers', () => { writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); } - function* getNodes() { - const main = viewRoot.getChild( 0 ); + function* getNodes( childIndex = 0 ) { + const main = viewRoot.getChild( childIndex ); yield main; for ( const { item } of controller.view.createRangeIn( main ) ) { diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slotconversion.js index 895398f331b..0765377aff3 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.js +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.js @@ -87,12 +87,11 @@ function getBoxUpcastConverter( editor ) { } function downcastBox( modelElement, conversionApi ) { - const { writer } = conversionApi; + const { writer, slotFor } = conversionApi; const viewBox = writer.createContainerElement( 'div', { class: 'box' } ); - conversionApi.mapper.bindElements( modelElement, viewBox ); - const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } ); + writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap ); for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) { @@ -117,38 +116,7 @@ function downcastBox( modelElement, conversionApi ) { } } - for ( const field of modelElement.getChildren() ) { - const viewField = writer.createContainerElement( 'div', { class: 'box-content-field' } ); - - writer.insert( writer.createPositionAt( contentWrap, field.index ), viewField ); - conversionApi.mapper.bindElements( field, viewField ); - conversionApi.consumable.consume( field, 'insert' ); - - // Might be simplified to: - // - // writer.defineSlot( field, viewField, field.index ); - // - // but would require a converter: - // - // editor.conversion.for( 'downcast' ).elementToElement( { // .slotToElement()? - // model: 'viewField', - // view: { name: 'div', class: 'box-content-field' } - // } ); - } - - // At this point we're inserting whole "component". Equivalent to (JSX-like notation): - // - // "rendered" view Mapping/source - // - // <-- top-level box - // ... box[meta.header] - // - // ... <-- this is "slot" boxField - // ... many - // ... <-- this is "slot" boxField - // - // ... box[meta.author] - // + writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( modelElement, 'children' ) ); return viewBox; } @@ -199,7 +167,7 @@ function Box( editor ) { editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) ); - editor.conversion.for( 'downcast' ).elementToElement( { + editor.conversion.for( 'downcast' ).elementToStructure( { model: 'box', view: downcastBox, triggerBy: { @@ -208,6 +176,11 @@ function Box( editor ) { } } ); + editor.conversion.for( 'downcast' ).elementToElement( { + model: 'boxField', + view: { name: 'div', classes: 'box-content-field' } + } ); + addBoxMetaButton( editor, 'boxTitle', 'Box title', () => ( { header: { title: `Random title no. ${ getRandom() }.` } } ) ); From 73af915e4527f0206b54c4026a12af32d918c357 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 6 Aug 2021 20:37:53 +0200 Subject: [PATCH 002/140] The elementToElement with a backward compatible reconversion support. --- .../src/conversion/downcasthelpers.js | 95 ++++-- .../tests/manual/elementreconversion.html | 68 +++++ .../tests/manual/elementreconversion.js | 274 ++++++++++++++++++ .../tests/manual/elementreconversion.md | 12 + .../tests/manual/slotconversion.html | 3 + 5 files changed, 420 insertions(+), 32 deletions(-) create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.html create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.js create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.md diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 27c9a1d5f55..c1f7ab9d030 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -833,21 +833,12 @@ export function insertElement( elementCreator ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - // Fill with nested view nodes. - for ( const modelChildNode of data.item.getChildren() ) { - const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); - - if ( data.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { - conversionApi.writer.move( - conversionApi.writer.createRangeOn( viewChildNode ), - conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); - // conversionApi.mapper.bindElements( modelChildNode, viewChildNode ); - } else { - // TODO this should be exposed by conversionApi? - // Note that this is not creating another consumable, it's using the current one. - conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); - } + // Trigger recursive reinsertion if the element creator created more than a single element. + if ( !viewElement.isEmpty ) { + // TODO this is a legacy reconversion and might throw an error instead of handling this case. + reinsertRecursive( data.item, conversionApi ); + } else { + reinsertChildren( data.item, conversionApi ); } }; } @@ -920,22 +911,7 @@ function insertStructure( elementCreator ) { // TODO use temporary modelToViewPosition mapping mapper.bindElements( element, slot ); - for ( const modelChildNode of element.getChildren() ) { - const viewChildNode = elementOrTextProxyToView( modelChildNode, mapper ); - - if ( data.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { // TODO ??? - writer.move( - writer.createRangeOn( viewChildNode ), - mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); - // console.log( 'reused old view for', modelChildNode ); - // mapper.bindElements( modelChildNode, viewChildNode ); - } else { - // TODO this should be exposed by conversionApi? - // Note that this is not creating another consumable, it's using the current one. - conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); - } - } + reinsertChildren( element, conversionApi ); writer.move( writer.createRangeIn( slot ), writer.createPositionBefore( slot ) ); mapper.unbindViewElement( slot ); @@ -1903,9 +1879,64 @@ function createTriggerByHandler( config ) { }; } +// TODO +function reinsertChildren( modelElement, conversionApi ) { + const viewElement = conversionApi.mapper.toViewElement( modelElement ); + + // Fill with nested view nodes. + for ( const modelChildNode of modelElement.getChildren() ) { + const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); + + if ( viewChildNode && viewChildNode.root != viewElement.root ) { + conversionApi.writer.move( + conversionApi.writer.createRangeOn( viewChildNode ), + conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + } else { + // TODO this should be exposed by conversionApi? + // Note that this is not creating another consumable, it's using the current one. + conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); + } + } +} + +// TODO docs +// TODO this is the old way of reconverting (but tuned to make it recursive instead of iterating over the range) +function reinsertRecursive( modelElement, conversionApi ) { + const viewElement = conversionApi.mapper.toViewElement( modelElement ); + + const items = Array.from( modelElement.getChildren() ); + + // Iterate over children of reconverted element in order to... + for ( const modelChildNode of items ) { + const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); + + // ...either bring back previously converted view... + if ( viewChildNode ) { + // Do not move views that are already in converted element - those might be created by the main element converter + // in case when main element converts also its direct children. + if ( viewChildNode.root != viewElement.root ) { + conversionApi.writer.move( + conversionApi.writer.createRangeOn( viewChildNode ), + conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + } else { + // ... but then check if there are any children of it to reinsert. + items.push( ...modelChildNode.getChildren() ); + } + } + // ... or by converting newly inserted elements. + else { + // TODO this should be exposed by conversionApi? + // Note that this is not creating another consumable, it's using the current one. + conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); + } + } +} + // TODO function elementOrTextProxyToView( item, mapper ) { - if ( item.is( '$text' ) ) { + if ( item.is( '$text' ) || item.is( '$textProxy' ) ) { const viewPosition = mapper.toViewPosition( ModelPosition._createBefore( item ) ); const viewPositionParent = viewPosition.parent; diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.html b/packages/ckeditor5-engine/tests/manual/elementreconversion.html new file mode 100644 index 00000000000..f044323c506 --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.html @@ -0,0 +1,68 @@ + + + + +

elementToElement

+

Generates view for box and boxFields in a single converter

+ +
+
+
+
+
+
+

I'm a title

+
+
+
+

Foo

+
+
    +
  • Bar
  • +
+
+

Baz

+
+
+ @john +
+
+
+
diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js new file mode 100644 index 00000000000..5b6e04bac90 --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.js @@ -0,0 +1,274 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, window, document */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; + +const byClassName = className => element => element.hasClass( className ); + +const getRandom = () => parseInt( Math.random() * 1000 ); + +function mapMeta( editor ) { + return metaElement => { + if ( metaElement.hasClass( 'box-meta-header' ) ) { + const title = getChildren( editor, metaElement ) + .filter( byClassName( 'box-meta-header-title' ) ) + .pop().getChild( 0 ).getChild( 0 ).data; + + return { + header: { + title + } + }; + } + + if ( metaElement.hasClass( 'box-meta-author' ) ) { + const link = metaElement.getChild( 0 ); + + return { + author: { + name: link.getChild( 0 ).data, + website: link.getAttribute( 'href' ) + } + }; + } + }; +} + +function getChildren( editor, viewElement ) { + return [ ...( editor.editing.view.createRangeIn( viewElement ) ) ] + .filter( ( { type } ) => type === 'elementStart' ) + .map( ( { item } ) => item ); +} + +function getBoxUpcastConverter( editor ) { + return dispatcher => dispatcher.on( 'element:div', ( event, data, conversionApi ) => { + const viewElement = data.viewItem; + const writer = conversionApi.writer; + + if ( !viewElement.hasClass( 'box' ) ) { + return; + } + + const box = writer.createElement( 'box' ); + + if ( !conversionApi.safeInsert( box, data.modelCursor ) ) { + return; + } + + const elements = getChildren( editor, viewElement ); + + const fields = elements.filter( byClassName( 'box-content-field' ) ); + const metaElements = elements.filter( byClassName( 'box-meta' ) ); + + const meta = metaElements.map( mapMeta( editor ) ).reduce( ( prev, current ) => Object.assign( prev, current ), {} ); + + writer.setAttribute( 'meta', meta, box ); + + for ( const field of fields ) { + const boxField = writer.createElement( 'boxField' ); + + conversionApi.safeInsert( boxField, writer.createPositionAt( box, field.index ) ); + conversionApi.convertChildren( field, boxField ); + } + + conversionApi.consumable.consume( viewElement, { name: true } ); + elements.map( element => { + conversionApi.consumable.consume( element, { name: true } ); + } ); + + conversionApi.updateConversionResult( box, data ); + } ); +} + +function downcastBox( modelElement, conversionApi ) { + const { writer } = conversionApi; + + const viewBox = writer.createContainerElement( 'div', { class: 'box' } ); + const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } ); + + conversionApi.mapper.bindElements( modelElement, viewBox ); + + writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap ); + + for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) { + if ( meta === 'header' ) { + const header = writer.createRawElement( 'div', { + class: 'box-meta box-meta-header' + }, domElement => { + domElement.innerHTML = `

${ metaValue.title }

`; + } ); + + writer.insert( writer.createPositionBefore( contentWrap ), header ); + } + + if ( meta === 'author' ) { + const author = writer.createRawElement( 'div', { + class: 'box-meta box-meta-author' + }, domElement => { + domElement.innerHTML = `${ metaValue.name }`; + } ); + + writer.insert( writer.createPositionAfter( contentWrap ), author ); + } + } + + for ( const field of modelElement.getChildren() ) { + const viewField = writer.createContainerElement( 'div', { class: 'box-content-field' } ); + + writer.insert( writer.createPositionAt( contentWrap, field.index ), viewField ); + conversionApi.mapper.bindElements( field, viewField ); + conversionApi.consumable.consume( field, 'insert' ); + } + + return viewBox; +} + +function addButton( editor, uiName, label, callback ) { + editor.ui.componentFactory.add( uiName, locale => { + const view = new ButtonView( locale ); + + view.set( { label, withText: true } ); + + view.listenTo( view, 'execute', () => { + const parent = editor.model.document.selection.getFirstPosition().parent; + const boxField = parent.findAncestor( 'boxField' ); + + if ( !boxField ) { + return; + } + + editor.model.change( writer => callback( writer, boxField.findAncestor( 'box' ), boxField ) ); + } ); + + return view; + } ); +} + +function addBoxMetaButton( editor, uiName, label, updateWith ) { + addButton( editor, uiName, label, ( writer, box ) => { + writer.setAttribute( 'meta', { + ...box.getAttribute( 'meta' ), + ...updateWith() + }, box ); + } ); +} + +function Box( editor ) { + editor.model.schema.register( 'box', { + allowIn: '$root', + isObject: true, + isSelectable: true, + allowAttributes: [ 'infoBoxMeta' ] + } ); + + editor.model.schema.register( 'boxField', { + allowContentOf: '$root', + allowIn: 'box', + isLimit: true + } ); + + editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) ); + + // editor.conversion.for( 'downcast' ).elementToStructure( { + editor.conversion.for( 'downcast' ).elementToElement( { + model: 'box', + view: downcastBox, + triggerBy: { + attributes: [ 'meta' ], + children: [ 'boxField' ] + } + } ); + + // editor.conversion.for( 'downcast' ).elementToElement( { + // model: 'boxField', + // view: { name: 'div', classes: 'box-content-field' } + // } ); + + addBoxMetaButton( editor, 'boxTitle', 'Box title', () => ( { + header: { title: `Random title no. ${ getRandom() }.` } + } ) ); + + addBoxMetaButton( editor, 'boxAuthor', 'Box author', () => ( { + author: { + website: `www.example.com/${ getRandom() }`, + name: `Random author no. ${ getRandom() }` + } + } ) ); + + addButton( editor, 'addBoxField', '+', ( writer, box, boxField ) => { + const newBoxField = writer.createElement( 'boxField' ); + writer.insert( newBoxField, box, boxField.index ); + writer.insert( writer.createElement( 'paragraph' ), newBoxField, 0 ); + } ); + + addButton( editor, 'removeBoxField', '-', ( writer, box, boxField ) => { + writer.remove( boxField ); + } ); +} + +function AddRenderCount( editor ) { + let insertCount = 0; + + const nextInsert = () => insertCount++; + + editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert', ( event, data, conversionApi ) => { + const view = conversionApi.mapper.toViewElement( data.item ); + + if ( view ) { + const insertCount = nextInsert(); + + conversionApi.writer.setAttribute( 'data-insert-count', `${ insertCount }`, view ); + conversionApi.writer.setAttribute( 'title', `Insertion counter: ${ insertCount }`, view ); + } + }, { priority: 'lowest' } ) ); +} + +ClassicEditor + .create( document.querySelector( '#editor' ), { + plugins: [ ArticlePluginSet, Box, AddRenderCount ], + toolbar: [ + 'heading', + '|', + 'boxTitle', + 'boxAuthor', + 'addBoxField', + 'removeBoxField', + '|', + 'bold', + 'italic', + 'link', + 'bulletedList', + 'numberedList', + '|', + 'outdent', + 'indent', + '|', + 'blockQuote', + 'insertTable', + 'mediaEmbed', + 'undo', + 'redo' + ], + image: { + toolbar: [ 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + } + } ) + .then( editor => { + window.editor = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.md b/packages/ckeditor5-engine/tests/manual/elementreconversion.md new file mode 100644 index 00000000000..bab4d7202b1 --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.md @@ -0,0 +1,12 @@ +# Slot conversion + +The editor should be loaded with a "box" element that contains multiple "slots" in which user can edit content. + +An additional converter adds `"data-insert-count"` attribute to view elements to show when it was rendered. It is displayed with a CSS at the top-right corner of rendered element. If a view element was not re-rendered this attribute should not change. *Note*: it only acts on "insert" changes so it can omit attribute-to-element changes or insertions not passed through dispatcher. + +Observe which view elements are re-rendered when using UI-buttons: + +* `Box title` - updates title attribute which triggers re-rendering of a "box". +* `Box author` - updates author attribute which triggers re-rendering of a "box". +* `+` - adds "slot" to box" which triggers re-rendering of a "box". +* `-` - removes "slot" from box" which triggers re-rendering of a "box". diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index 7ecc38a4691..75df1f518ee 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -39,6 +39,9 @@ +

elementToStructure

+

Separate converters for box (with slots) and boxField

+
From dbe55714cef1de57178f67b845270a2ca9c7bdb5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 6 Aug 2021 23:55:56 +0200 Subject: [PATCH 003/140] Simplified slot conversion. --- .../src/conversion/downcastdispatcher.js | 14 +- .../src/conversion/downcasthelpers.js | 195 +++++++----------- .../tests/manual/slotconversion.html | 17 ++ .../tests/manual/slotconversion.js | 12 +- 4 files changed, 110 insertions(+), 128 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 77f995cdb4e..139dd03d27f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -149,10 +149,10 @@ export default class DowncastDispatcher { for ( const entry of changes ) { if ( entry.type === 'insert' ) { this.convertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); + } else if ( entry.type === 'reinsert' ) { + this.convertReinsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); } else if ( entry.type === 'remove' ) { this.convertRemove( entry.position, entry.length, entry.name, writer ); - } else if ( entry.type === 'reconvert' ) { - this.reconvertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); } else { // Defaults to 'attribute' change. this.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, writer ); @@ -264,22 +264,20 @@ export default class DowncastDispatcher { * * @fires insert * @fires attribute - * @param {module:engine/model/element~Element} element The element to be reconverted. + * @param {module:engine/model/range~Range} range The range to reinsert. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ - reconvertInsert( range, writer ) { + convertReinsert( range, writer ) { this.conversionApi.writer = writer; // Create a list of things that can be consumed, consisting of nodes and their attributes. this.conversionApi.consumable = this._createInsertConsumable( range ); // Convert the elements - without converting children. + // // Fire a separate insert event for each node and text fragment contained in the range. for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( { - ...data, - reconversion: true - } ); + this._convertInsertWithAttributes( data ); } this._clearConversionApi(); diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index c1f7ab9d030..bc5b44c76f2 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -838,7 +838,7 @@ export function insertElement( elementCreator ) { // TODO this is a legacy reconversion and might throw an error instead of handling this case. reinsertRecursive( data.item, conversionApi ); } else { - reinsertChildren( data.item, conversionApi ); + reinsertNodes( viewElement, data.item.getChildren(), conversionApi ); } }; } @@ -848,34 +848,31 @@ export function insertElement( elementCreator ) { */ function insertStructure( elementCreator ) { return ( evt, data, conversionApi ) => { - const { writer, mapper, consumable } = conversionApi; - - // Scan for elements and it's current mapped view elements. - const elements = Array.from( data.range.getItems( { shallow: true } ) ); - // const consumables = data.consumables || elements.map( element => [ element, 'insert' ] ); - const consumables = []; // TODO - - // Verify if all consumables are available to be consumed. - for ( const [ item, eventName ] of consumables ) { - if ( !consumable.test( item, eventName ) ) { - return; - } - } + let debugSlots = false; // eslint-disable-line + // @if CK_DEBUG_SLOTS // debugSlots = true; const slotsMap = new Map(); // View creation. - const viewElement = elementCreator( data.range, { + const viewElement = elementCreator( data.item, { ...conversionApi, - slotFor( element, mode = 'children' ) { - const slot = writer.createContainerElement( '$slot' ); + slotFor( mode ) { + const slot = debugSlots ? + conversionApi.writer.createContainerElement( 'div', { 'data-slot': String( mode ) } ) : + conversionApi.writer.createContainerElement( '$slot' ); - if ( slotsMap.has( element ) ) { - slotsMap.get( element ).push( { slot, mode } ); + if ( mode == 'children' ) { + slotsMap.set( slot, Array.from( data.item.getChildren() ) ); + } else if ( typeof mode == 'function' ) { + slotsMap.set( slot, Array.from( data.item.getChildren() ).filter( element => mode( element ) ) ); } else { - slotsMap.set( element, [ { slot, mode } ] ); + throw new Error( 'unknown slot mode' ); } + // TODO throw if the same element is in multiple slots + // TODO all child nodes must be covered here so they won't get converted separately + // TODO Make sure that slots are created for the elements themself or direct descendants and not other elements. + return slot; } } ); @@ -885,96 +882,54 @@ function insertStructure( elementCreator ) { return; } - // Consume consumables. - for ( const [ item, eventName ] of consumables ) { - if ( !consumable.consume( item, eventName ) ) { - return; - } + if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { + return; } - const viewPosition = mapper.toViewPosition( data.range.start ); + const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); + + conversionApi.mapper.bindElements( data.item, viewElement ); + conversionApi.writer.insert( viewPosition, viewElement ); - // TODO make sure that provided viewElement has no bindings (else there could be an infinite loop). + // Fill slots. - mapper.bindElements( elements[ 0 ], viewElement ); - writer.insert( viewPosition, viewElement ); + conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); - // mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); + let currentSlot = null; // Fill slots with nested view nodes. - for ( const [ element, elementSlots ] of slotsMap.entries() ) { - for ( const { slot, mode } of elementSlots ) { - // TODO Make sure that slots are created for the elements themself or direct descendants and not other elements. + for ( const [ slot, nodes ] of slotsMap ) { + currentSlot = slot; - if ( mode == 'children' ) { - // Bind slot with element so children will be down-casted into it. - // TODO use temporary modelToViewPosition mapping - mapper.bindElements( element, slot ); - - reinsertChildren( element, conversionApi ); - - writer.move( writer.createRangeIn( slot ), writer.createPositionBefore( slot ) ); - mapper.unbindViewElement( slot ); - writer.remove( slot ); - - // Rebind the original view element to the correct model element. - mapper.bindElements( element, viewElement ); - // } else if ( mode == 'self' ) { - // // const currentViewElement = currentMapping.get( element ); - // - // // Bind slot parents to the element so they could be found while reconverting. - // // TODO probably all parents that have no mapping yet (so excluding main viewElement) - // mapper.bindElements( element, slot.parent ); - // - // // if ( range.reconversion && currentViewElement && currentViewElement.root != viewElement.root ) { // TODO ??? - // // writer.move( - // // writer.createRangeOn( currentViewElement ), - // // mapper.toViewPosition( ModelPosition._createBefore( element ) ) - // // ); - // // console.log( 'reused old view for element at', - // ModelPosition._createBefore( element ).path, '->', currentViewElement ); - // // - // // // Rebind the original view element to the correct model element. - // // mapper.bindElements( element, currentViewElement ); - // // } else { - // // TODO this should be exposed by conversionApi? - // // Note that this is not creating another consumable, it's using the current one. - // conversionApi.dispatcher._convertInsert( ModelRange._createOn( element ) ); - // // } - // - // writer.remove( slot ); - } else { - // TODO callback check (for example tbody vs thead) - } + reinsertNodes( viewElement, nodes, conversionApi ); + + if ( !debugSlots ) { + conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); + conversionApi.writer.remove( slot ); } } - // mapper.off( 'modelToViewPosition', toViewPositionMapping ); - - // // After reconversion is done we can unbind the old view. - // for ( const currentView of currentViewElements ) { - // if ( currentView.root != viewElement.root ) { - // mapper.unbindViewElement( currentView ); - // } - // } - - // function toViewPositionMapping( evt, data ) { - // if ( data.isPhantom || data.viewPosition ) { - // return; - // } - // - // const elementSlots = slotsMap.get( data.modelPosition.nodeAfter ); - // - // if ( !elementSlots ) { - // return; - // } - // - // // TODO conditional slots? - // - // if ( elementSlots[ 0 ].mode == 'self' ) { - // data.viewPosition = writer.createPositionBefore( elementSlots[ 0 ].slot ); - // } - // } + conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping ); + + function toViewPositionMapping( evt, data ) { + if ( data.viewPosition || !currentSlot ) { + return; + } + + const nodeAfter = data.modelPosition.nodeAfter; + + if ( !nodeAfter ) { + return; + } + + const index = slotsMap.get( currentSlot ).findIndex( element => element == nodeAfter ); + + if ( index < 0 ) { + return; + } + + data.viewPosition = data.mapper.findPositionIn( currentSlot, index ); + } return viewElement; }; @@ -1543,10 +1498,7 @@ function downcastElementToElement( config ) { function downcastElementToStructure( config ) { config = cloneDeep( config ); - const elementCreator = normalizeToElementConfig( config.view, 'container' ); - - // TODO The elementCreator expects model element here but insertStructure operates on ranges. - config.view = ( range, ...rest ) => elementCreator( range.start.nodeAfter, ...rest ); + config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { dispatcher.on( 'insert:' + config.model, insertStructure( config.view ), { priority: config.converterPriority || 'normal' } ); @@ -1850,9 +1802,10 @@ function createTriggerByHandler( config ) { return; } } else { - if ( !config.triggerBy.children || !config.triggerBy.children.includes( diffItem.name ) ) { - return; - } + // TODO trigger reconversion on any child node change + // if ( !config.triggerBy.children || !config.triggerBy.children.includes( diffItem.name ) ) { + // return; + // } } // If it's already marked for reconversion, so skip this change. @@ -1866,10 +1819,12 @@ function createTriggerByHandler( config ) { reducedChanges.push( { type: 'remove', + name: element.name, position, length: 1 }, { - type: 'reconvert', // TODO is this needed? what about element renaming ( paragraph => heading1 ) + type: 'reinsert', + name: element.name, position, length: 1 } ); @@ -1880,18 +1835,18 @@ function createTriggerByHandler( config ) { } // TODO -function reinsertChildren( modelElement, conversionApi ) { - const viewElement = conversionApi.mapper.toViewElement( modelElement ); - +function reinsertNodes( viewElement, nodes, conversionApi ) { // Fill with nested view nodes. - for ( const modelChildNode of modelElement.getChildren() ) { + for ( const modelChildNode of nodes ) { const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); if ( viewChildNode && viewChildNode.root != viewElement.root ) { - conversionApi.writer.move( - conversionApi.writer.createRangeOn( viewChildNode ), - conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); + if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { + conversionApi.writer.move( + conversionApi.writer.createRangeOn( viewChildNode ), + conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + } } else { // TODO this should be exposed by conversionApi? // Note that this is not creating another consumable, it's using the current one. @@ -1916,10 +1871,12 @@ function reinsertRecursive( modelElement, conversionApi ) { // Do not move views that are already in converted element - those might be created by the main element converter // in case when main element converts also its direct children. if ( viewChildNode.root != viewElement.root ) { - conversionApi.writer.move( - conversionApi.writer.createRangeOn( viewChildNode ), - conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); + if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { + conversionApi.writer.move( + conversionApi.writer.createRangeOn( viewChildNode ), + conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); + } } else { // ... but then check if there are any children of it to reinsert. items.push( ...modelChildNode.getChildren() ); diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index 75df1f518ee..aa666f43305 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -36,6 +36,23 @@ padding: 0 2px; opacity: 0.8; } + .ck.ck-content div[data-slot] { + padding: 15px; + border: 1px dotted hsl(285, 100%, 50%); + background: hsla(285, 100%, 50%, .3); + position: relative; + } + .ck.ck-content div[data-slot]:after { + content: attr(data-slot); + position: absolute; + font-size: 10px; + top: -1px; + right: -1px; + color: hsl(0, 0%, 100%); + border: 1px solid hsl(285, 100%, 50%); + background: hsla(285, 100%, 50%, .6); + padding: 1px 5px; + } diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slotconversion.js index 0765377aff3..c0811c79336 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.js +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.js @@ -116,7 +116,17 @@ function downcastBox( modelElement, conversionApi ) { } } - writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( modelElement, 'children' ) ); + // switch for testing filtered vs all children + if ( 0 ) { + writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( 'children' ) ); + } else { + writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( element => element.index < 2 ) ); + + const contentWrap2 = writer.createContainerElement( 'div', { class: 'box-content' } ); + + writer.insert( writer.createPositionAt( viewBox, 'end' ), contentWrap2 ); + writer.insert( writer.createPositionAt( contentWrap2, 0 ), slotFor( element => element.index >= 2 ) ); + } return viewBox; } From 0ad44b918ed51c1b510b80bb715509055cd644ee Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 9 Aug 2021 19:56:07 +0200 Subject: [PATCH 004/140] Reconversion could be triggered by some ancestor change. --- .../src/conversion/downcastdispatcher.js | 27 ++- .../src/conversion/downcasthelpers.js | 156 ++++++++++++------ .../tests/manual/elementreconversion.js | 11 +- .../tests/manual/slotconversion.html | 12 ++ .../tests/manual/slotconversion.js | 10 +- 5 files changed, 150 insertions(+), 66 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 139dd03d27f..f767b1656c4 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -137,13 +137,8 @@ export default class DowncastDispatcher { this.convertMarkerRemove( change.name, change.range, writer ); } - const reduceChangesData = { - changes: differ.getChanges() - }; - - this.fire( 'reduceChanges', reduceChangesData ); - - const changes = reduceChangesData.changes; + // Let features modify the change list (for example to allow reconversion). + const changes = this._reduceChanges( differ.getChanges() ); // Convert changes that happened on model tree. for ( const entry of changes ) { @@ -171,6 +166,7 @@ export default class DowncastDispatcher { this.convertMarkerAdd( change.name, change.range, writer ); } + // Remove mappings for all removed view elements. this.conversionApi.mapper.flushTemporaryMappings(); } @@ -277,7 +273,7 @@ export default class DowncastDispatcher { // // Fire a separate insert event for each node and text fragment contained in the range. for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( data ); + this._convertInsertWithAttributes( { ...data, reconversion: true } ); } this._clearConversionApi(); @@ -436,6 +432,21 @@ export default class DowncastDispatcher { } } + /** + * TODO + * + * @private + * @param {Iterable.} changes + * @returns {Iterable.} + */ + _reduceChanges( changes ) { + const data = { changes }; + + this.fire( 'reduceChanges', data ); + + return data.changes; + } + /** * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume from a given range, * assuming that the range has just been inserted to the model. diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index bc5b44c76f2..d0b39bde015 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -836,9 +836,9 @@ export function insertElement( elementCreator ) { // Trigger recursive reinsertion if the element creator created more than a single element. if ( !viewElement.isEmpty ) { // TODO this is a legacy reconversion and might throw an error instead of handling this case. - reinsertRecursive( data.item, conversionApi ); + reinsertRecursive( data.item, conversionApi, data.reconversion ); } else { - reinsertNodes( viewElement, data.item.getChildren(), conversionApi ); + reinsertNodes( viewElement, data.item.getChildren(), conversionApi, data.reconversion ); } }; } @@ -886,6 +886,8 @@ function insertStructure( elementCreator ) { return; } + // TODO consume attributes that were watched to trigger this conversion + const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); conversionApi.mapper.bindElements( data.item, viewElement ); @@ -901,7 +903,7 @@ function insertStructure( elementCreator ) { for ( const [ slot, nodes ] of slotsMap ) { currentSlot = slot; - reinsertNodes( viewElement, nodes, conversionApi ); + reinsertNodes( viewElement, nodes, conversionApi, data.reconversion ); if ( !debugSlots ) { conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); @@ -922,7 +924,7 @@ function insertStructure( elementCreator ) { return; } - const index = slotsMap.get( currentSlot ).findIndex( element => element == nodeAfter ); + const index = slotsMap.get( currentSlot ).indexOf( nodeAfter ); if ( index < 0 ) { return; @@ -930,8 +932,6 @@ function insertStructure( elementCreator ) { data.viewPosition = data.mapper.findPositionIn( currentSlot, index ); } - - return viewElement; }; } @@ -1483,13 +1483,32 @@ function removeHighlight( highlightDescriptor ) { function downcastElementToElement( config ) { config = cloneDeep( config ); + config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { - dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); + dispatcher.on( 'insert:' + config.model.name, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); + + if ( config.model.children || config.model.attributes.length ) { + dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); + } if ( config.triggerBy ) { - dispatcher.on( 'reduceChanges', createTriggerByHandler( config ) ); + dispatcher.on( 'reduceChanges', createChangeReducer( config.model, ( node, change ) => { + if ( !config.triggerBy( node, change ) ) { + return null; + } + + const elements = []; + + for ( const { item } of ModelRange._createOn( node ) ) { + if ( item.is( 'element', config.model.name ) ) { + elements.push( item ); + } + } + + return { elements, keepChange: true }; + } ) ); } }; } @@ -1498,13 +1517,14 @@ function downcastElementToElement( config ) { function downcastElementToStructure( config ) { config = cloneDeep( config ); + config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { - dispatcher.on( 'insert:' + config.model, insertStructure( config.view ), { priority: config.converterPriority || 'normal' } ); + dispatcher.on( 'insert:' + config.model.name, insertStructure( config.view ), { priority: config.converterPriority || 'normal' } ); - if ( config.triggerBy ) { - dispatcher.on( 'reduceChanges', createTriggerByHandler( config ) ); + if ( config.model.children || config.model.attributes.length ) { + dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); } }; } @@ -1653,6 +1673,25 @@ function downcastMarkerToHighlight( config ) { }; } +// TODO +function normalizeModelElementConfig( model ) { + if ( typeof model == 'string' ) { + model = { name: model }; + } + + // List of attributes that should trigger reconversion. + if ( !model.attributes ) { + model.attributes = []; + } else if ( !Array.isArray( model.attributes ) ) { + model.attributes = [ model.attributes ]; + } + + // Whether a children insertion/deletion should trigger reconversion. + model.children = !!model.children; + + return model; +} + // Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it // to a function (because lower level converters accept only element creator functions). // @@ -1783,51 +1822,74 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) { } // TODO -function createTriggerByHandler( config ) { +function createChangeReducerCallback( model ) { + return ( node, change ) => { + if ( !node.is( 'element', model.name ) ) { + return null; + } + + if ( change.type == 'attribute' ) { + if ( !model.attributes.includes( change.attributeKey ) ) { + return null; + } + } else { + if ( !model.children ) { + return null; + } + } + + return { elements: [ node ] }; + }; +} + +// TODO +function createChangeReducer( model, callback = createChangeReducerCallback( model ) ) { return ( evt, data ) => { const reducedChanges = []; - const reconvertedElements = new Set(); - for ( const diffItem of data.changes ) { + if ( !data.reconvertedElements ) { + data.reconvertedElements = new Set(); + } + + for ( const change of data.changes ) { // For attribute use node affected by the change. // For insert or remove use parent element because we need to check if it's added/removed child. - const element = diffItem.position ? diffItem.position.parent : diffItem.range.start.nodeAfter; + const node = change.position ? change.position.parent : change.range.start.nodeAfter; - if ( element.name != config.model ) { - return; - } + const { elements, keepChange } = callback( node, change ) || {}; - if ( diffItem.type == 'attribute' ) { - if ( !config.triggerBy.attributes || !config.triggerBy.attributes.includes( diffItem.attributeKey ) ) { - return; - } - } else { - // TODO trigger reconversion on any child node change - // if ( !config.triggerBy.children || !config.triggerBy.children.includes( diffItem.name ) ) { - // return; - // } - } + if ( !elements ) { + reducedChanges.push( change ); - // If it's already marked for reconversion, so skip this change. - if ( reconvertedElements.has( element ) ) { continue; } - reconvertedElements.add( element ); + if ( keepChange ) { + reducedChanges.push( change ); + } - const position = ModelPosition._createBefore( element ); + for ( const element of elements ) { + // If it's already marked for reconversion, so skip this change. + if ( data.reconvertedElements.has( element ) ) { + continue; + } - reducedChanges.push( { - type: 'remove', - name: element.name, - position, - length: 1 - }, { - type: 'reinsert', - name: element.name, - position, - length: 1 - } ); + data.reconvertedElements.add( element ); + + const position = ModelPosition._createBefore( element ); + + reducedChanges.push( { + type: 'remove', + name: element.name, + position, + length: 1 + }, { + type: 'reinsert', + name: element.name, + position, + length: 1 + } ); + } } data.changes = reducedChanges; @@ -1835,12 +1897,12 @@ function createTriggerByHandler( config ) { } // TODO -function reinsertNodes( viewElement, nodes, conversionApi ) { +function reinsertNodes( viewElement, nodes, conversionApi, reconversion ) { // Fill with nested view nodes. for ( const modelChildNode of nodes ) { const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); - if ( viewChildNode && viewChildNode.root != viewElement.root ) { + if ( reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { conversionApi.writer.move( conversionApi.writer.createRangeOn( viewChildNode ), @@ -1857,7 +1919,7 @@ function reinsertNodes( viewElement, nodes, conversionApi ) { // TODO docs // TODO this is the old way of reconverting (but tuned to make it recursive instead of iterating over the range) -function reinsertRecursive( modelElement, conversionApi ) { +function reinsertRecursive( modelElement, conversionApi, reconversion ) { const viewElement = conversionApi.mapper.toViewElement( modelElement ); const items = Array.from( modelElement.getChildren() ); @@ -1867,7 +1929,7 @@ function reinsertRecursive( modelElement, conversionApi ) { const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); // ...either bring back previously converted view... - if ( viewChildNode ) { + if ( reconversion && viewChildNode ) { // Do not move views that are already in converted element - those might be created by the main element converter // in case when main element converts also its direct children. if ( viewChildNode.root != viewElement.root ) { diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js index 5b6e04bac90..fb7c9c02a57 100644 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.js +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.js @@ -175,14 +175,13 @@ function Box( editor ) { editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) ); - // editor.conversion.for( 'downcast' ).elementToStructure( { editor.conversion.for( 'downcast' ).elementToElement( { - model: 'box', - view: downcastBox, - triggerBy: { + model: { + name: 'box', attributes: [ 'meta' ], - children: [ 'boxField' ] - } + children: true + }, + view: downcastBox } ); // editor.conversion.for( 'downcast' ).elementToElement( { diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index aa666f43305..64881ec086e 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -60,6 +60,18 @@

elementToStructure

Separate converters for box (with slots) and boxField

+ + + + + + + + + + +
ab
cd
+
diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slotconversion.js index c0811c79336..2512bd42dc7 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.js +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.js @@ -178,12 +178,12 @@ function Box( editor ) { editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) ); editor.conversion.for( 'downcast' ).elementToStructure( { - model: 'box', - view: downcastBox, - triggerBy: { + model: { + name: 'box', attributes: [ 'meta' ], - children: [ 'boxField' ] - } + children: true + }, + view: downcastBox } ); editor.conversion.for( 'downcast' ).elementToElement( { From 490c3024de8bdf9284387290cc0d36e1d6230b07 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 9 Aug 2021 19:58:28 +0200 Subject: [PATCH 005/140] Table downcast by reconversion. --- .../src/converters/downcast.js | 408 +++--------------- .../table-cell-refresh-post-fixer.js | 75 ---- .../table-heading-rows-refresh-post-fixer.js | 55 --- packages/ckeditor5-table/src/tableediting.js | 78 ++-- 4 files changed, 100 insertions(+), 516 deletions(-) delete mode 100644 packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js delete mode 100644 packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index ea93c40f4d8..c4b529fcaee 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -12,26 +12,10 @@ import { setHighlightHandling, toWidget, toWidgetEditable } from 'ckeditor5/src/ import { toArray } from 'ckeditor5/src/utils'; /** - * Model table element to view table element conversion helper. - * - * This conversion helper creates the whole table element with child elements. - * - * @param {Object} options - * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. - * @returns {Function} Conversion helper. + * TODO */ -export function downcastInsertTable( options = {} ) { - return dispatcher => dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => { - const table = data.item; - - if ( !conversionApi.consumable.consume( table, 'insert' ) ) { - return; - } - - // Consume attributes if present to not fire attribute change downcast - conversionApi.consumable.consume( table, 'attribute:headingRows:table' ); - conversionApi.consumable.consume( table, 'attribute:headingColumns:table' ); - +export function downcastTable( tableUtils, options = {} ) { + return ( table, conversionApi ) => { const asWidget = options && options.asWidget; const figureElement = conversionApi.writer.createContainerElement( 'figure', { class: 'table' } ); @@ -44,111 +28,49 @@ export function downcastInsertTable( options = {} ) { tableWidget = toTableWidget( figureElement, conversionApi.writer ); } - const tableWalker = new TableWalker( table ); - const tableAttributes = { headingRows: table.getAttribute( 'headingRows' ) || 0, headingColumns: table.getAttribute( 'headingColumns' ) || 0 }; - // Cache for created table rows. - const viewRows = new Map(); - - for ( const tableSlot of tableWalker ) { - const { row, cell } = tableSlot; + // Table head slot. + if ( tableAttributes.headingRows > 0 ) { + const tableHead = conversionApi.writer.createContainerElement( 'thead' ); - const tableRow = table.getChild( row ); - const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); - viewRows.set( row, trElement ); + const headSlot = conversionApi.slotFor( + element => element.is( 'element', 'tableRow' ) && element.index < tableAttributes.headingRows + ); - // Consume table cell - it will be always consumed as we convert whole table at once. - conversionApi.consumable.consume( cell, 'insert' ); - - const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); - - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ); + conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableElement, 'end' ), tableHead ); + conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableHead, 0 ), headSlot ); } - // Insert empty TR elements if there are any rows without anchored cells. Since the model is always normalized - // this can happen only in the document fragment that only part of the table is down-casted. - for ( const tableRow of table.getChildren() ) { - const rowIndex = tableRow.index; - - // Make sure that this is a table row and not some other element (i.e., caption). - if ( tableRow.is( 'element', 'tableRow' ) && !viewRows.has( rowIndex ) ) { - viewRows.set( rowIndex, createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) ); - } - } + // Table body slot. + if ( tableAttributes.headingRows < tableUtils.getRows( table ) ) { + const tableBody = conversionApi.writer.createContainerElement( 'tbody' ); - const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); + const bodySlot = conversionApi.slotFor( + element => element.is( 'element', 'tableRow' ) && element.index >= tableAttributes.headingRows + ); - conversionApi.mapper.bindElements( table, asWidget ? tableWidget : figureElement ); - conversionApi.writer.insert( viewPosition, asWidget ? tableWidget : figureElement ); - } ); -} - -/** - * Model row element to view `` element conversion helper. - * - * This conversion helper creates the whole `` element with child elements. - * - * @returns {Function} Conversion helper. - */ -export function downcastInsertRow() { - return dispatcher => dispatcher.on( 'insert:tableRow', ( evt, data, conversionApi ) => { - const tableRow = data.item; - - if ( !conversionApi.consumable.consume( tableRow, 'insert' ) ) { - return; + conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableElement, 'end' ), tableBody ); + conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableBody, 0 ), bodySlot ); } - const table = tableRow.parent; - - const figureElement = conversionApi.mapper.toViewElement( table ); - const tableElement = getViewTable( figureElement ); - - const row = table.getChildIndex( tableRow ); - - const tableWalker = new TableWalker( table, { row } ); - - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; + // Slot for the rest (for example caption). + const restSlot = conversionApi.slotFor( element => !element.is( 'element', 'tableRow' ) ); - // Cache for created table rows. - const viewRows = new Map(); + conversionApi.writer.insert( conversionApi.writer.createPositionAt( figureElement, 'end' ), restSlot ); - for ( const tableSlot of tableWalker ) { - const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); - viewRows.set( row, trElement ); - - // Consume table cell - it will be always consumed as we convert whole row at once. - conversionApi.consumable.consume( tableSlot.cell, 'insert' ); - - const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); - - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); - } - } ); + return asWidget ? tableWidget : figureElement; + }; } /** - * Model table cell element to view `` or `` element conversion helper. - * - * This conversion helper will create proper `` elements for table cells that are in the heading section (heading row or column) - * and `` otherwise. - * - * @returns {Function} Conversion helper. + * TODO */ -export function downcastInsertCell() { - return dispatcher => dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => { - const tableCell = data.item; - - if ( !conversionApi.consumable.consume( tableCell, 'insert' ) ) { - return; - } - +export function downcastCell( options = {} ) { + return ( tableCell, conversionApi ) => { const tableRow = tableCell.parent; const table = tableRow.parent; const rowIndex = table.getChildIndex( tableRow ); @@ -163,78 +85,10 @@ export function downcastInsertCell() { // We need to iterate over a table in order to get proper row & column values from a walker for ( const tableSlot of tableWalker ) { if ( tableSlot.cell === tableCell ) { - const trElement = conversionApi.mapper.toViewElement( tableRow ); - const insertPosition = conversionApi.writer.createPositionAt( trElement, tableRow.getChildIndex( tableCell ) ); - - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); - - // No need to iterate further. - return; + return createViewTableCellElement( tableSlot, tableAttributes, conversionApi, options ); } } - } ); -} - -/** - * Conversion helper that acts on heading column table attribute change. - * - * Depending on changed attributes this converter will rename `` elements or vice versa depending on the cell column index. - * - * @returns {Function} Conversion helper. - */ -export function downcastTableHeadingColumnsChange() { - return dispatcher => dispatcher.on( 'attribute:headingColumns:table', ( evt, data, conversionApi ) => { - const table = data.item; - - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } - - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; - - const oldColumns = data.attributeOldValue; - const newColumns = data.attributeNewValue; - - const lastColumnToCheck = ( oldColumns > newColumns ? oldColumns : newColumns ) - 1; - - for ( const tableSlot of new TableWalker( table, { endColumn: lastColumnToCheck } ) ) { - renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ); - } - } ); -} - -/** - * Conversion helper that acts on a removed row. - * - * @returns {Function} Conversion helper. - */ -export function downcastRemoveRow() { - return dispatcher => dispatcher.on( 'remove:tableRow', ( evt, data, conversionApi ) => { - // Prevent default remove converter. - evt.stop(); - const viewWriter = conversionApi.writer; - const mapper = conversionApi.mapper; - - const viewStart = mapper.toViewPosition( data.position ).getLastMatchingPosition( value => !value.item.is( 'element', 'tr' ) ); - const viewItem = viewStart.nodeAfter; - const tableSection = viewItem.parent; - const viewTable = tableSection.parent; - - // Remove associated from the view. - const removeRange = viewWriter.createRangeOn( viewItem ); - const removed = viewWriter.remove( removeRange ); - - for ( const child of viewWriter.createRangeIn( removed ).getItems() ) { - mapper.unbindViewElement( child ); - } - - // Cleanup: Ensure that thead & tbody sections are removed if left empty after removing rows. See #6437, #6391. - removeTableSectionIfEmpty( 'thead', viewTable, conversionApi ); - removeTableSectionIfEmpty( 'tbody', viewTable, conversionApi ); - }, { priority: 'higher' } ); + }; } /** @@ -250,18 +104,27 @@ export function downcastRemoveRow() { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi * @returns {module:engine/view/containerelement~ContainerElement|undefined} */ -export function convertParagraphInTableCell( modelElement, conversionApi ) { - const { writer } = conversionApi; +export function convertParagraphInTableCell( options = {} ) { + return ( modelElement, conversionApi ) => { + const asWidget = options && options.asWidget; + const { writer } = conversionApi; - if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { - return; - } + if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { + return; + } - if ( isSingleParagraphWithoutAttributes( modelElement ) ) { - return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); - } else { - return writer.createContainerElement( 'p' ); - } + if ( isSingleParagraphWithoutAttributes( modelElement ) ) { + if ( asWidget ) { + return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); + } else { + // Additional requirement for data pipeline to have backward compatible data tables. + conversionApi.consumable.consume( modelElement, 'insert' ); + conversionApi.mapper.bindElements( modelElement, conversionApi.mapper.toViewElement( modelElement.parent ) ); + + return null; + } + } + }; } /** @@ -297,61 +160,12 @@ function toTableWidget( viewElement, writer ) { return toWidget( viewElement, writer, { hasSelectionHandle: true } ); } -// Renames an existing table cell in the view to a given element name. -// -// **Note** This method will not do anything if a view table cell has not been converted yet. -// -// @param {module:engine/model/element~Element} tableCell -// @param {String} desiredCellElementName -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function renameViewTableCell( tableCell, desiredCellElementName, conversionApi ) { - const viewWriter = conversionApi.writer; - const viewCell = conversionApi.mapper.toViewElement( tableCell ); - - const editable = viewWriter.createEditableElement( desiredCellElementName, viewCell.getAttributes() ); - const renamedCell = toWidgetEditable( editable, viewWriter ); - - setHighlightHandling( - renamedCell, - viewWriter, - ( element, descriptor, writer ) => writer.addClass( toArray( descriptor.classes ), element ), - ( element, descriptor, writer ) => writer.removeClass( toArray( descriptor.classes ), element ) - ); - - viewWriter.insert( viewWriter.createPositionAfter( viewCell ), renamedCell ); - viewWriter.move( viewWriter.createRangeIn( viewCell ), viewWriter.createPositionAt( renamedCell, 0 ) ); - viewWriter.remove( viewWriter.createRangeOn( viewCell ) ); - - conversionApi.mapper.unbindViewElement( viewCell ); - conversionApi.mapper.bindElements( tableCell, renamedCell ); -} - -// Renames a table cell element in the view according to its location in the table. -// -// @param {module:table/tablewalker~TableSlot} tableSlot -// @param {{headingColumns, headingRows}} tableAttributes -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ) { - const { cell } = tableSlot; - - // Check whether current columnIndex is overlapped by table cells from previous rows. - const desiredCellElementName = getCellElementName( tableSlot, tableAttributes ); - - const viewCell = conversionApi.mapper.toViewElement( cell ); - - // If in single change we're converting attribute changes and inserting cell the table cell might not be inserted into view - // because of child conversion is done after parent. - if ( viewCell && viewCell.name !== desiredCellElementName ) { - renameViewTableCell( cell, desiredCellElementName, conversionApi ); - } -} - // Creates a table cell element in the view. // // @param {module:table/tablewalker~TableSlot} tableSlot -// @param {module:engine/view/position~Position} insertPosition +// TODO // @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ) { +function createViewTableCellElement( tableSlot, tableAttributes, conversionApi, options ) { const asWidget = options && options.asWidget; const cellElementName = getCellElementName( tableSlot, tableAttributes ); @@ -368,52 +182,7 @@ function createViewTableCellElement( tableSlot, tableAttributes, insertPosition, ); } - const tableCell = tableSlot.cell; - - const firstChild = tableCell.getChild( 0 ); - const isSingleParagraph = tableCell.childCount === 1 && firstChild.name === 'paragraph'; - - conversionApi.writer.insert( insertPosition, cellElement ); - - conversionApi.mapper.bindElements( tableCell, cellElement ); - - // Additional requirement for data pipeline to have backward compatible data tables. - if ( !asWidget && isSingleParagraph && !hasAnyAttribute( firstChild ) ) { - const innerParagraph = tableCell.getChild( 0 ); - - conversionApi.consumable.consume( innerParagraph, 'insert' ); - - conversionApi.mapper.bindElements( innerParagraph, cellElement ); - } -} - -// Creates a `` view element. -// -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/model/element~Element} tableRow -// @param {Number} rowIndex -// @param {{headingColumns, headingRows}} tableAttributes -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @returns {module:engine/view/element~Element} -function createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) { - // Will always consume since we're converting element from a parent . - conversionApi.consumable.consume( tableRow, 'insert' ); - - const trElement = tableRow.isEmpty ? - conversionApi.writer.createEmptyElement( 'tr' ) : - conversionApi.writer.createContainerElement( 'tr' ); - - conversionApi.mapper.bindElements( tableRow, trElement ); - - const headingRows = tableAttributes.headingRows; - const tableSection = getOrCreateTableSection( getSectionName( rowIndex, tableAttributes ), tableElement, conversionApi ); - - const offset = headingRows > 0 && rowIndex >= headingRows ? rowIndex - headingRows : rowIndex; - const position = conversionApi.writer.createPositionAt( tableSection, offset ); - - conversionApi.writer.insert( position, trElement ); - - return trElement; + return cellElement; } // Returns `th` for heading cells and `td` for other cells for the current table walker value. @@ -439,81 +208,6 @@ function getCellElementName( tableSlot, tableAttributes ) { return isRowHeading ? 'th' : 'td'; } -// Returns the table section name for the current table walker value. -// -// @param {Number} row -// @param {{headingColumns, headingRows}} tableAttributes -// @returns {String} -function getSectionName( row, tableAttributes ) { - return row < tableAttributes.headingRows ? 'thead' : 'tbody'; -} - -// Creates or returns an existing `` or `` element with caching. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} viewTable -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @param {Object} cachedTableSection An object that stores cached elements. -// @returns {module:engine/view/containerelement~ContainerElement} -function getOrCreateTableSection( sectionName, viewTable, conversionApi ) { - const viewTableSection = getExistingTableSectionElement( sectionName, viewTable ); - - return viewTableSection ? viewTableSection : createTableSection( sectionName, viewTable, conversionApi ); -} - -// Finds an existing `` or `` element or returns undefined. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function getExistingTableSectionElement( sectionName, tableElement ) { - for ( const tableSection of tableElement.getChildren() ) { - if ( tableSection.name == sectionName ) { - return tableSection; - } - } -} - -// Creates a table section at the end of the table. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @returns {module:engine/view/containerelement~ContainerElement} -function createTableSection( sectionName, tableElement, conversionApi ) { - const tableChildElement = conversionApi.writer.createContainerElement( sectionName ); - - const insertPosition = conversionApi.writer.createPositionAt( tableElement, sectionName == 'tbody' ? 'end' : 0 ); - - conversionApi.writer.insert( insertPosition, tableChildElement ); - - return tableChildElement; -} - -// Removes an existing `` or `` element if it is empty. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function removeTableSectionIfEmpty( sectionName, tableElement, conversionApi ) { - const tableSection = getExistingTableSectionElement( sectionName, tableElement ); - - if ( tableSection && tableSection.childCount === 0 ) { - conversionApi.writer.remove( conversionApi.writer.createRangeOn( tableSection ) ); - } -} - -// Finds a '
' element inside the `
` widget. -// -// @param {module:engine/view/element~Element} viewFigure -function getViewTable( viewFigure ) { - for ( const child of viewFigure.getChildren() ) { - if ( child.name === 'table' ) { - return child; - } - } -} - // Checks if an element has any attributes set. // // @param {module:engine/model/element~Element element diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js deleted file mode 100644 index c132e3a329a..00000000000 --- a/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module table/converters/table-cell-refresh-post-fixer - */ - -import { isSingleParagraphWithoutAttributes } from './downcast'; - -/** - * Injects a table cell post-fixer into the model which marks the table cell in the differ to have it re-rendered. - * - * Model `paragraph` inside a table cell can be rendered as `` or `

`. It is rendered as `` if this is the only block - * element in that table cell and it does not have any attributes. It is rendered as `

` otherwise. - * - * When table cell content changes, for example a second `paragraph` element is added, we need to ensure that the first `paragraph` is - * re-rendered so it changes from `` to `

`. The easiest way to do it is to re-render the entire table cell. - * - * @param {module:engine/model/model~Model} model - * @param {module:engine/conversion/mapper~Mapper} mapper - */ -export default function injectTableCellRefreshPostFixer( model, mapper ) { - model.document.registerPostFixer( () => tableCellRefreshPostFixer( model.document.differ, mapper ) ); -} - -function tableCellRefreshPostFixer( differ, mapper ) { - // Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes. - - // 1. Gather all changes inside table cell. - const cellsToCheck = new Set(); - - for ( const change of differ.getChanges() ) { - const parent = change.type == 'attribute' ? change.range.start.parent : change.position.parent; - - if ( parent.is( 'element', 'tableCell' ) ) { - cellsToCheck.add( parent ); - } - } - - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: Checking table cell to refresh (${ cellsToCheck.size }).` ); - // @if CK_DEBUG_TABLE // let paragraphsRefreshed = 0; - - for ( const tableCell of cellsToCheck.values() ) { - for ( const paragraph of [ ...tableCell.getChildren() ].filter( child => shouldRefresh( child, mapper ) ) ) { - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing paragraph in table cell (${++paragraphsRefreshed}).` ); - differ.refreshItem( paragraph ); - } - } - - // Always return false to prevent the refresh post-fixer from re-running on the same set of changes and going into an infinite loop. - // This "post-fixer" does not change the model structure so there shouldn't be need to run other post-fixers again. - // See https://github.com/ckeditor/ckeditor5/issues/1936 & https://github.com/ckeditor/ckeditor5/issues/8200. - return false; -} - -// Check if given model element needs refreshing. -// -// @param {module:engine/model/element~Element} modelElement -// @param {module:engine/conversion/mapper~Mapper} mapper -// @returns {Boolean} -function shouldRefresh( child, mapper ) { - if ( !child.is( 'element', 'paragraph' ) ) { - return false; - } - - const viewElement = mapper.toViewElement( child ); - - if ( !viewElement ) { - return false; - } - - return isSingleParagraphWithoutAttributes( child ) !== viewElement.is( 'element', 'span' ); -} diff --git a/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js deleted file mode 100644 index b3810fd6cfb..00000000000 --- a/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module table/converters/table-heading-rows-refresh-post-fixer - */ - -/** - * Injects a table post-fixer into the model which marks the table in the differ to have it re-rendered. - * - * Table heading rows are represented in the model by a `headingRows` attribute. However, in the view, it's represented as separate - * sections of the table (`

` or ``) and changing `headingRows` attribute requires moving table rows between two sections. - * This causes problems with structural changes in a table (like adding and removing rows) thus atomic converters cannot be used. - * - * When table `headingRows` attribute changes, the entire table is re-rendered. - * - * @param {module:engine/model/model~Model} model - */ -export default function injectTableHeadingRowsRefreshPostFixer( model ) { - model.document.registerPostFixer( () => tableHeadingRowsRefreshPostFixer( model ) ); -} - -function tableHeadingRowsRefreshPostFixer( model ) { - const differ = model.document.differ; - - // Stores tables to be refreshed so the table will be refreshed once for multiple changes. - const tablesToRefresh = new Set(); - - for ( const change of differ.getChanges() ) { - if ( change.type != 'attribute' ) { - continue; - } - - const element = change.range.start.nodeAfter; - - if ( element && element.is( 'element', 'table' ) && change.attributeKey == 'headingRows' ) { - tablesToRefresh.add( element ); - } - } - - if ( tablesToRefresh.size ) { - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing heading rows (${ tablesToRefresh.size }).` ); - - for ( const table of tablesToRefresh.values() ) { - // Should be handled by a `triggerBy` configuration. See: https://github.com/ckeditor/ckeditor5/issues/8138. - differ.refreshItem( table ); - } - - return true; - } - - return false; -} diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index 838d1643f16..413c86ffa52 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -9,15 +9,9 @@ import { Plugin } from 'ckeditor5/src/core'; +import TableUtils from '../src/tableutils'; import upcastTable, { ensureParagraphInTableCell, skipEmptyTableRow, upcastTableFigure } from './converters/upcasttable'; -import { - convertParagraphInTableCell, - downcastInsertCell, - downcastInsertRow, - downcastInsertTable, - downcastRemoveRow, - downcastTableHeadingColumnsChange -} from './converters/downcast'; +import { convertParagraphInTableCell, downcastCell, downcastTable } from './converters/downcast'; import InsertTableCommand from './commands/inserttablecommand'; import InsertRowCommand from './commands/insertrowcommand'; @@ -31,12 +25,9 @@ import SetHeaderColumnCommand from './commands/setheadercolumncommand'; import MergeCellsCommand from './commands/mergecellscommand'; import SelectRowCommand from './commands/selectrowcommand'; import SelectColumnCommand from './commands/selectcolumncommand'; -import TableUtils from '../src/tableutils'; import injectTableLayoutPostFixer from './converters/table-layout-post-fixer'; import injectTableCellParagraphPostFixer from './converters/table-cell-paragraph-post-fixer'; -import injectTableCellRefreshPostFixer from './converters/table-cell-refresh-post-fixer'; -import injectTableHeadingRowsRefreshPostFixer from './converters/table-heading-rows-refresh-post-fixer'; import '../theme/tableediting.css'; @@ -53,6 +44,13 @@ export default class TableEditing extends Plugin { return 'TableEditing'; } + /** + * @inheritDoc + */ + static get requires() { + return [ TableUtils ]; + } + /** * @inheritDoc */ @@ -61,6 +59,7 @@ export default class TableEditing extends Plugin { const model = editor.model; const schema = model.schema; const conversion = editor.conversion; + const tableUtils = editor.plugins.get( TableUtils ); schema.register( 'table', { allowWhere: '$block', @@ -88,15 +87,28 @@ export default class TableEditing extends Plugin { // Table conversion. conversion.for( 'upcast' ).add( upcastTable() ); - conversion.for( 'editingDowncast' ).add( downcastInsertTable( { asWidget: true } ) ); - conversion.for( 'dataDowncast' ).add( downcastInsertTable() ); + conversion.for( 'editingDowncast' ).elementToStructure( { + model: { + name: 'table', + attributes: [ 'headingRows' ], + children: true + }, + view: downcastTable( tableUtils, { asWidget: true } ) + } ); + conversion.for( 'dataDowncast' ).elementToStructure( { + model: { + name: 'table', + attributes: [ 'headingRows' ], + children: true + }, + view: downcastTable( tableUtils ) + } ); // Table row conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); conversion.for( 'upcast' ).add( skipEmptyTableRow() ); - conversion.for( 'editingDowncast' ).add( downcastInsertRow() ); - conversion.for( 'editingDowncast' ).add( downcastRemoveRow() ); + conversion.for( 'downcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); // Table cell conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'td' } ); @@ -104,12 +116,32 @@ export default class TableEditing extends Plugin { conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'td' ) ); conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'th' ) ); - conversion.for( 'editingDowncast' ).add( downcastInsertCell() ); + conversion.for( 'editingDowncast' ).elementToElement( { + model: 'tableCell', + view: downcastCell( { asWidget: true } ), + triggerBy: ( node, change ) => ( + node.is( 'element', 'table' ) && change.type == 'attribute' && + [ 'headingRows', 'headingColumns' ].includes( change.attributeKey ) + ) + } ); + conversion.for( 'dataDowncast' ).elementToElement( { + model: 'tableCell', + view: downcastCell() + } ); // Duplicates code - needed to properly refresh paragraph inside a table cell. conversion.for( 'editingDowncast' ).elementToElement( { model: 'paragraph', - view: convertParagraphInTableCell, + view: convertParagraphInTableCell( { asWidget: true } ), + converterPriority: 'high', + triggerBy: ( node, change ) => ( + node.is( 'element', 'tableCell' ) && change.type != 'attribute' || + node.is( 'element', 'paragraph' ) && node.parent.is( 'element', 'tableCell' ) && change.type == 'attribute' + ) + } ); + conversion.for( 'dataDowncast' ).elementToElement( { + model: 'paragraph', + view: convertParagraphInTableCell(), converterPriority: 'high' } ); @@ -126,9 +158,6 @@ export default class TableEditing extends Plugin { view: 'rowspan' } ); - // Table heading columns conversion (a change of heading rows requires a reconversion of the whole table). - conversion.for( 'editingDowncast' ).add( downcastTableHeadingColumnsChange() ); - // Manually adjust model position mappings in a special case, when a table cell contains a paragraph, which is bound // to its parent (to the table cell). This custom model-to-view position mapping is necessary in data pipeline only, // because only during this conversion a paragraph can be bound to its parent. @@ -164,18 +193,9 @@ export default class TableEditing extends Plugin { editor.commands.add( 'selectTableRow', new SelectRowCommand( editor ) ); editor.commands.add( 'selectTableColumn', new SelectColumnCommand( editor ) ); - injectTableHeadingRowsRefreshPostFixer( model ); injectTableLayoutPostFixer( model ); - injectTableCellRefreshPostFixer( model, editor.editing.mapper ); injectTableCellParagraphPostFixer( model ); } - - /** - * @inheritDoc - */ - static get requires() { - return [ TableUtils ]; - } } // Creates a mapper callback to adjust model position mappings in a table cell containing a paragraph, which is bound to its parent From 280d2c547a76ca840e6ef92b24b463cce58d4543 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 10 Aug 2021 16:33:35 +0200 Subject: [PATCH 006/140] Updated reconversion trigger conditions for table paragraph. --- .../src/conversion/downcasthelpers.js | 28 +++++++++++++++++-- packages/ckeditor5-table/src/tableediting.js | 28 +++++++++++++++---- .../tests/converters/downcast.js | 4 ++- .../table-cell-refresh-post-fixer.js | 4 +-- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index d0b39bde015..6a0332a27a0 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -824,12 +824,27 @@ export function insertElement( elementCreator ) { return; } + // TODO throw if viewElement has any children - this should be handled by toStructure converters + if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { return; } const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); + // Bring back the old view element if it's same as the previous one. + // TODO this is useless if any of the attributes are converted separately + // but helps with replacing table cells that do not need to be replaced (but still useless for col/row span). + if ( data.reconversion ) { + const oldViewElement = conversionApi.mapper.toViewElement( data.item ); + + if ( oldViewElement && oldViewElement.isSimilar( viewElement ) && oldViewElement.root != viewPosition.root ) { + conversionApi.writer.move( conversionApi.writer.createRangeOn( oldViewElement ), viewPosition ); + + return; + } + } + conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); @@ -1846,6 +1861,7 @@ function createChangeReducerCallback( model ) { function createChangeReducer( model, callback = createChangeReducerCallback( model ) ) { return ( evt, data ) => { const reducedChanges = []; + const additionalChanges = []; if ( !data.reconvertedElements ) { data.reconvertedElements = new Set(); @@ -1869,6 +1885,14 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod } for ( const element of elements ) { + // Do not reconvert just inserted elements. + // This is needed for the reconversion triggered by some other change (for example a paragraph inside a table cell). + const positionBefore = ModelPosition._createBefore( element ); + + if ( data.changes.find( change => change.type == 'insert' && change.position.isEqual( positionBefore ) ) ) { + continue; + } + // If it's already marked for reconversion, so skip this change. if ( data.reconvertedElements.has( element ) ) { continue; @@ -1878,7 +1902,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod const position = ModelPosition._createBefore( element ); - reducedChanges.push( { + ( keepChange ? additionalChanges : reducedChanges ).push( { type: 'remove', name: element.name, position, @@ -1892,7 +1916,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod } } - data.changes = reducedChanges; + data.changes = [ ...reducedChanges, ...additionalChanges ]; }; } diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index 413c86ffa52..79843ba9b37 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -108,7 +108,12 @@ export default class TableEditing extends Plugin { conversion.for( 'upcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); conversion.for( 'upcast' ).add( skipEmptyTableRow() ); - conversion.for( 'downcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); + conversion.for( 'downcast' ).elementToElement( { + model: 'tableRow', + view: ( modelElement, { writer } ) => ( + modelElement.isEmpty ? writer.createEmptyElement( 'tr' ) : writer.createContainerElement( 'tr' ) + ) + } ); // Table cell conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'td' } ); @@ -133,11 +138,22 @@ export default class TableEditing extends Plugin { conversion.for( 'editingDowncast' ).elementToElement( { model: 'paragraph', view: convertParagraphInTableCell( { asWidget: true } ), - converterPriority: 'high', - triggerBy: ( node, change ) => ( - node.is( 'element', 'tableCell' ) && change.type != 'attribute' || - node.is( 'element', 'paragraph' ) && node.parent.is( 'element', 'tableCell' ) && change.type == 'attribute' - ) + triggerBy: ( node, change ) => { + if ( node.is( 'element', 'tableCell' ) ) { + const isReducedToSingleParagraph = node.childCount == 1 && change.type == 'remove'; + const isExtendedFromSingleParagraph = node.childCount > 1 && change.type == 'insert' && change.position.offset <= 1; + + return isReducedToSingleParagraph || isExtendedFromSingleParagraph; + } else if ( change.type == 'attribute' && node.is( 'element', 'paragraph' ) && node.parent.is( 'element', 'tableCell' ) ) { + const isSingleChildElement = node.parent.childCount == 1; + const isAttributeSetOrClear = change.attributeOldValue == null || change.attributeNewValue == null; + + // TODO maybe we should be able to access the old attributes (from differ) + // to check if there were no attributes and now there is any + return isSingleChildElement && isAttributeSetOrClear; + } + }, + converterPriority: 'high' } ); conversion.for( 'dataDowncast' ).elementToElement( { model: 'paragraph', diff --git a/packages/ckeditor5-table/tests/converters/downcast.js b/packages/ckeditor5-table/tests/converters/downcast.js index 9375ab48c2a..245eb493d16 100644 --- a/packages/ckeditor5-table/tests/converters/downcast.js +++ b/packages/ckeditor5-table/tests/converters/downcast.js @@ -213,7 +213,7 @@ describe( 'downcast converters', () => { assertEqualMarkup( editor.getData(), '
' + - '' + + '' + '

 

 
' ); } ); @@ -473,6 +473,8 @@ describe( 'downcast converters', () => { model.change( writer => { const row = writer.createElement( 'tableRow' ); + writer.setAttribute( 'headingRows', 3, table ); + writer.insert( row, table, 1 ); writer.insertElement( 'tableCell', row, 'end' ); diff --git a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js index 9481853d89f..b66a8302730 100644 --- a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js +++ b/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js @@ -297,7 +297,7 @@ describe( 'Table cell refresh post-fixer', () => { assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ [ '

00

' ] ], { asWidget: true } ) ); - expect( getViewForParagraph( table ) ).to.equal( previousView ); + expect( getViewForParagraph( table ) ).to.not.equal( previousView ); } ); it( 'should keep

in the view when adding another attribute to a and removing attribute that is already set', () => { @@ -314,7 +314,7 @@ describe( 'Table cell refresh post-fixer', () => { assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ [ '

00

' ] ], { asWidget: true } ) ); - expect( getViewForParagraph( table ) ).to.equal( previousView ); + expect( getViewForParagraph( table ) ).to.not.equal( previousView ); } ); it( 'should keep

in the view when attribute value is changed (table cell with multiple blocks)', () => { From d0f0bd0340374c6a9b24e803b5fe08c850475c85 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 10 Aug 2021 18:23:43 +0200 Subject: [PATCH 007/140] Removed support for legacy reconversion and simplified code for reinserting elements (text nodes are not supported). --- .../src/conversion/downcasthelpers.js | 58 +--- .../tests/manual/elementreconversion.html | 68 ----- .../tests/manual/elementreconversion.js | 273 ------------------ .../tests/manual/elementreconversion.md | 12 - 4 files changed, 2 insertions(+), 409 deletions(-) delete mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.html delete mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.js delete mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.md diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 6a0332a27a0..500a1845c56 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -848,13 +848,7 @@ export function insertElement( elementCreator ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - // Trigger recursive reinsertion if the element creator created more than a single element. - if ( !viewElement.isEmpty ) { - // TODO this is a legacy reconversion and might throw an error instead of handling this case. - reinsertRecursive( data.item, conversionApi, data.reconversion ); - } else { - reinsertNodes( viewElement, data.item.getChildren(), conversionApi, data.reconversion ); - } + reinsertNodes( viewElement, data.item.getChildren(), conversionApi, data.reconversion ); }; } @@ -1924,7 +1918,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod function reinsertNodes( viewElement, nodes, conversionApi, reconversion ) { // Fill with nested view nodes. for ( const modelChildNode of nodes ) { - const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); + const viewChildNode = conversionApi.mapper.toViewElement( modelChildNode ); if ( reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { @@ -1941,54 +1935,6 @@ function reinsertNodes( viewElement, nodes, conversionApi, reconversion ) { } } -// TODO docs -// TODO this is the old way of reconverting (but tuned to make it recursive instead of iterating over the range) -function reinsertRecursive( modelElement, conversionApi, reconversion ) { - const viewElement = conversionApi.mapper.toViewElement( modelElement ); - - const items = Array.from( modelElement.getChildren() ); - - // Iterate over children of reconverted element in order to... - for ( const modelChildNode of items ) { - const viewChildNode = elementOrTextProxyToView( modelChildNode, conversionApi.mapper ); - - // ...either bring back previously converted view... - if ( reconversion && viewChildNode ) { - // Do not move views that are already in converted element - those might be created by the main element converter - // in case when main element converts also its direct children. - if ( viewChildNode.root != viewElement.root ) { - if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { - conversionApi.writer.move( - conversionApi.writer.createRangeOn( viewChildNode ), - conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); - } - } else { - // ... but then check if there are any children of it to reinsert. - items.push( ...modelChildNode.getChildren() ); - } - } - // ... or by converting newly inserted elements. - else { - // TODO this should be exposed by conversionApi? - // Note that this is not creating another consumable, it's using the current one. - conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); - } - } -} - -// TODO -function elementOrTextProxyToView( item, mapper ) { - if ( item.is( '$text' ) || item.is( '$textProxy' ) ) { - const viewPosition = mapper.toViewPosition( ModelPosition._createBefore( item ) ); - const viewPositionParent = viewPosition.parent; - - return viewPositionParent.is( '$text' ) ? viewPositionParent : null; - } - - return mapper.toViewElement( item ); -} - /** * An object describing how the marker highlight should be represented in the view. * diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.html b/packages/ckeditor5-engine/tests/manual/elementreconversion.html deleted file mode 100644 index f044323c506..00000000000 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - -

elementToElement

-

Generates view for box and boxFields in a single converter

- -
-
-
-
-
-
-

I'm a title

-
-
-
-

Foo

-
-
    -
  • Bar
  • -
-
-

Baz

-
-
- @john -
-
-
-
diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js deleted file mode 100644 index fb7c9c02a57..00000000000 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/* globals console, window, document */ - -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; -import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; -import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; - -const byClassName = className => element => element.hasClass( className ); - -const getRandom = () => parseInt( Math.random() * 1000 ); - -function mapMeta( editor ) { - return metaElement => { - if ( metaElement.hasClass( 'box-meta-header' ) ) { - const title = getChildren( editor, metaElement ) - .filter( byClassName( 'box-meta-header-title' ) ) - .pop().getChild( 0 ).getChild( 0 ).data; - - return { - header: { - title - } - }; - } - - if ( metaElement.hasClass( 'box-meta-author' ) ) { - const link = metaElement.getChild( 0 ); - - return { - author: { - name: link.getChild( 0 ).data, - website: link.getAttribute( 'href' ) - } - }; - } - }; -} - -function getChildren( editor, viewElement ) { - return [ ...( editor.editing.view.createRangeIn( viewElement ) ) ] - .filter( ( { type } ) => type === 'elementStart' ) - .map( ( { item } ) => item ); -} - -function getBoxUpcastConverter( editor ) { - return dispatcher => dispatcher.on( 'element:div', ( event, data, conversionApi ) => { - const viewElement = data.viewItem; - const writer = conversionApi.writer; - - if ( !viewElement.hasClass( 'box' ) ) { - return; - } - - const box = writer.createElement( 'box' ); - - if ( !conversionApi.safeInsert( box, data.modelCursor ) ) { - return; - } - - const elements = getChildren( editor, viewElement ); - - const fields = elements.filter( byClassName( 'box-content-field' ) ); - const metaElements = elements.filter( byClassName( 'box-meta' ) ); - - const meta = metaElements.map( mapMeta( editor ) ).reduce( ( prev, current ) => Object.assign( prev, current ), {} ); - - writer.setAttribute( 'meta', meta, box ); - - for ( const field of fields ) { - const boxField = writer.createElement( 'boxField' ); - - conversionApi.safeInsert( boxField, writer.createPositionAt( box, field.index ) ); - conversionApi.convertChildren( field, boxField ); - } - - conversionApi.consumable.consume( viewElement, { name: true } ); - elements.map( element => { - conversionApi.consumable.consume( element, { name: true } ); - } ); - - conversionApi.updateConversionResult( box, data ); - } ); -} - -function downcastBox( modelElement, conversionApi ) { - const { writer } = conversionApi; - - const viewBox = writer.createContainerElement( 'div', { class: 'box' } ); - const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } ); - - conversionApi.mapper.bindElements( modelElement, viewBox ); - - writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap ); - - for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) { - if ( meta === 'header' ) { - const header = writer.createRawElement( 'div', { - class: 'box-meta box-meta-header' - }, domElement => { - domElement.innerHTML = `

${ metaValue.title }

`; - } ); - - writer.insert( writer.createPositionBefore( contentWrap ), header ); - } - - if ( meta === 'author' ) { - const author = writer.createRawElement( 'div', { - class: 'box-meta box-meta-author' - }, domElement => { - domElement.innerHTML = `${ metaValue.name }`; - } ); - - writer.insert( writer.createPositionAfter( contentWrap ), author ); - } - } - - for ( const field of modelElement.getChildren() ) { - const viewField = writer.createContainerElement( 'div', { class: 'box-content-field' } ); - - writer.insert( writer.createPositionAt( contentWrap, field.index ), viewField ); - conversionApi.mapper.bindElements( field, viewField ); - conversionApi.consumable.consume( field, 'insert' ); - } - - return viewBox; -} - -function addButton( editor, uiName, label, callback ) { - editor.ui.componentFactory.add( uiName, locale => { - const view = new ButtonView( locale ); - - view.set( { label, withText: true } ); - - view.listenTo( view, 'execute', () => { - const parent = editor.model.document.selection.getFirstPosition().parent; - const boxField = parent.findAncestor( 'boxField' ); - - if ( !boxField ) { - return; - } - - editor.model.change( writer => callback( writer, boxField.findAncestor( 'box' ), boxField ) ); - } ); - - return view; - } ); -} - -function addBoxMetaButton( editor, uiName, label, updateWith ) { - addButton( editor, uiName, label, ( writer, box ) => { - writer.setAttribute( 'meta', { - ...box.getAttribute( 'meta' ), - ...updateWith() - }, box ); - } ); -} - -function Box( editor ) { - editor.model.schema.register( 'box', { - allowIn: '$root', - isObject: true, - isSelectable: true, - allowAttributes: [ 'infoBoxMeta' ] - } ); - - editor.model.schema.register( 'boxField', { - allowContentOf: '$root', - allowIn: 'box', - isLimit: true - } ); - - editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) ); - - editor.conversion.for( 'downcast' ).elementToElement( { - model: { - name: 'box', - attributes: [ 'meta' ], - children: true - }, - view: downcastBox - } ); - - // editor.conversion.for( 'downcast' ).elementToElement( { - // model: 'boxField', - // view: { name: 'div', classes: 'box-content-field' } - // } ); - - addBoxMetaButton( editor, 'boxTitle', 'Box title', () => ( { - header: { title: `Random title no. ${ getRandom() }.` } - } ) ); - - addBoxMetaButton( editor, 'boxAuthor', 'Box author', () => ( { - author: { - website: `www.example.com/${ getRandom() }`, - name: `Random author no. ${ getRandom() }` - } - } ) ); - - addButton( editor, 'addBoxField', '+', ( writer, box, boxField ) => { - const newBoxField = writer.createElement( 'boxField' ); - writer.insert( newBoxField, box, boxField.index ); - writer.insert( writer.createElement( 'paragraph' ), newBoxField, 0 ); - } ); - - addButton( editor, 'removeBoxField', '-', ( writer, box, boxField ) => { - writer.remove( boxField ); - } ); -} - -function AddRenderCount( editor ) { - let insertCount = 0; - - const nextInsert = () => insertCount++; - - editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert', ( event, data, conversionApi ) => { - const view = conversionApi.mapper.toViewElement( data.item ); - - if ( view ) { - const insertCount = nextInsert(); - - conversionApi.writer.setAttribute( 'data-insert-count', `${ insertCount }`, view ); - conversionApi.writer.setAttribute( 'title', `Insertion counter: ${ insertCount }`, view ); - } - }, { priority: 'lowest' } ) ); -} - -ClassicEditor - .create( document.querySelector( '#editor' ), { - plugins: [ ArticlePluginSet, Box, AddRenderCount ], - toolbar: [ - 'heading', - '|', - 'boxTitle', - 'boxAuthor', - 'addBoxField', - 'removeBoxField', - '|', - 'bold', - 'italic', - 'link', - 'bulletedList', - 'numberedList', - '|', - 'outdent', - 'indent', - '|', - 'blockQuote', - 'insertTable', - 'mediaEmbed', - 'undo', - 'redo' - ], - image: { - toolbar: [ 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] - }, - table: { - contentToolbar: [ - 'tableColumn', - 'tableRow', - 'mergeTableCells' - ] - } - } ) - .then( editor => { - window.editor = editor; - } ) - .catch( err => { - console.error( err.stack ); - } ); diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.md b/packages/ckeditor5-engine/tests/manual/elementreconversion.md deleted file mode 100644 index bab4d7202b1..00000000000 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.md +++ /dev/null @@ -1,12 +0,0 @@ -# Slot conversion - -The editor should be loaded with a "box" element that contains multiple "slots" in which user can edit content. - -An additional converter adds `"data-insert-count"` attribute to view elements to show when it was rendered. It is displayed with a CSS at the top-right corner of rendered element. If a view element was not re-rendered this attribute should not change. *Note*: it only acts on "insert" changes so it can omit attribute-to-element changes or insertions not passed through dispatcher. - -Observe which view elements are re-rendered when using UI-buttons: - -* `Box title` - updates title attribute which triggers re-rendering of a "box". -* `Box author` - updates author attribute which triggers re-rendering of a "box". -* `+` - adds "slot" to box" which triggers re-rendering of a "box". -* `-` - removes "slot" from box" which triggers re-rendering of a "box". From e8ab9b7c6179fdd74685a7c8f3b7c10dc8588899 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 12 Aug 2021 15:38:42 +0200 Subject: [PATCH 008/140] Cleaned DowncastDispatcher API. --- .../src/controller/datacontroller.js | 16 +- .../src/conversion/downcastdispatcher.js | 195 +++++++++--------- .../src/conversion/downcasthelpers.js | 3 +- .../ckeditor5-engine/src/dev-utils/model.js | 22 +- .../tests/conversion/downcastdispatcher.js | 92 ++++----- .../tests/conversion/downcasthelpers.js | 28 +-- packages/ckeditor5-heading/src/title.js | 19 +- 7 files changed, 177 insertions(+), 198 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js index f89d9567687..885097845ce 100644 --- a/packages/ckeditor5-engine/src/controller/datacontroller.js +++ b/packages/ckeditor5-engine/src/controller/datacontroller.js @@ -241,13 +241,7 @@ export default class DataController { this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment ); - // Make additional options available during conversion process through `conversionApi`. - this.downcastDispatcher.conversionApi.options = options; - - // We have no view controller and rendering to DOM in DataController so view.change() block is not used here. - this.downcastDispatcher.convertInsert( modelRange, viewWriter ); - - // Convert markers. + // Prepare list of markers. // For document fragment, simply take the markers assigned to this document fragment. // For model root, all markers in that root will be taken. // For model element, we need to check which markers are intersecting with this element and relatively modify the markers' ranges. @@ -256,12 +250,8 @@ export default class DataController { Array.from( modelElementOrFragment.markers ) : _getMarkersRelativeToElement( modelElementOrFragment ); - for ( const [ name, range ] of markers ) { - this.downcastDispatcher.convertMarkerAdd( name, range, viewWriter ); - } - - // Clean `conversionApi`. - delete this.downcastDispatcher.conversionApi.options; + // We have no view controller and rendering to DOM in DataController so view.change() block is not used here. + this.downcastDispatcher.convertInsert( modelRange, markers, viewWriter, options ); return viewDocumentFragment; } diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index f767b1656c4..69aa3adcde0 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -119,9 +119,10 @@ export default class DowncastDispatcher { /** * An interface passed by the dispatcher to the event callbacks. * + * @private * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi} */ - this.conversionApi = Object.assign( { dispatcher: this }, conversionApi ); + this.conversionApi = { dispatcher: this, ...conversionApi }; } /** @@ -132,9 +133,11 @@ export default class DowncastDispatcher { * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ convertChanges( differ, markers, writer ) { + const conversionApi = this._prepareConversionApi( writer ); + // Before the view is updated, remove markers which have changed. for ( const change of differ.getMarkersToRemove() ) { - this.convertMarkerRemove( change.name, change.range, writer ); + this._convertMarkerRemove( change.name, change.range, conversionApi ); } // Let features modify the change list (for example to allow reconversion). @@ -143,31 +146,31 @@ export default class DowncastDispatcher { // Convert changes that happened on model tree. for ( const entry of changes ) { if ( entry.type === 'insert' ) { - this.convertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); + this._convertInsert( Range._createFromPositionAndShift( entry.position, entry.length ), conversionApi ); } else if ( entry.type === 'reinsert' ) { - this.convertReinsert( Range._createFromPositionAndShift( entry.position, entry.length ), writer ); + this._convertReinsert( Range._createFromPositionAndShift( entry.position, entry.length ), conversionApi ); } else if ( entry.type === 'remove' ) { - this.convertRemove( entry.position, entry.length, entry.name, writer ); + this._convertRemove( entry.position, entry.length, entry.name, conversionApi ); } else { // Defaults to 'attribute' change. - this.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, writer ); + this._convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, conversionApi ); } } - for ( const markerName of this.conversionApi.mapper.flushUnboundMarkerNames() ) { + for ( const markerName of conversionApi.mapper.flushUnboundMarkerNames() ) { const markerRange = markers.get( markerName ).getRange(); - this.convertMarkerRemove( markerName, markerRange, writer ); - this.convertMarkerAdd( markerName, markerRange, writer ); + this._convertMarkerRemove( markerName, markerRange, conversionApi ); + this._convertMarkerAdd( markerName, markerRange, conversionApi ); } // After the view is updated, convert markers which have changed. for ( const change of differ.getMarkersToAdd() ) { - this.convertMarkerAdd( change.name, change.range, writer ); + this._convertMarkerAdd( change.name, change.range, conversionApi ); } // Remove mappings for all removed view elements. - this.conversionApi.mapper.flushTemporaryMappings(); + conversionApi.mapper.flushTemporaryMappings(); } /** @@ -180,32 +183,51 @@ export default class DowncastDispatcher { * @fires attribute * @param {module:engine/model/range~Range} range The inserted range. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. + * @param {Object} [options] TODO */ - convertInsert( range, writer ) { - this.conversionApi.writer = writer; + convertInsert( range, markers, writer, options = {} ) { + const conversionApi = this._prepareConversionApi( writer, options ); - // Create a list of things that can be consumed, consisting of nodes and their attributes. - this.conversionApi.consumable = this._createInsertConsumable( range ); + this._convertInsert( range, conversionApi ); - this._convertInsert( range ); + for ( const [ name, range ] of markers ) { + this._convertMarkerAdd( name, range, conversionApi ); + } + } - this._clearConversionApi(); + /** + * Starts a conversion of a range insertion. + * + * For each node in the range, {@link #event:insert `insert` event is fired}. For each attribute on each node, + * {@link #event:attribute `attribute` event is fired}. + * + * @protected + * @fires insert + * @fires attribute + * @param {module:engine/model/range~Range} range The inserted range. + * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. + */ + _convertInsert( range, conversionApi ) { + // Create a list of things that can be consumed, consisting of nodes and their attributes. + this._addConsumablesForInsert( conversionApi.consumable, range ); + + // Fire a separate insert event for each node and text fragment contained in the range. + for ( const data of Array.from( range ).map( walkerValueToEventData ) ) { + this._convertInsertWithAttributes( data, conversionApi ); + } } /** * Fires conversion of a single node removal. Fires {@link #event:remove remove event} with provided data. * + * @protected * @param {module:engine/model/position~Position} position Position from which node was removed. * @param {Number} length Offset size of removed node. * @param {String} name Name of removed node. * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document. */ - convertRemove( position, length, name, writer ) { - this.conversionApi.writer = writer; - - this.fire( 'remove:' + name, { position, length }, this.conversionApi ); - - this._clearConversionApi(); + _convertRemove( position, length, name, conversionApi ) { + this.fire( 'remove:' + name, { position, length }, conversionApi ); } /** @@ -213,6 +235,7 @@ export default class DowncastDispatcher { * * For each node in the given `range`, {@link #event:attribute attribute event} is fired with the passed data. * + * @protected * @fires attribute * @param {module:engine/model/range~Range} range Changed range. * @param {String} key Key of the attribute that has changed. @@ -220,11 +243,9 @@ export default class DowncastDispatcher { * @param {*} newValue New attribute value or `null` if the attribute has been removed. * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document. */ - convertAttribute( range, key, oldValue, newValue, writer ) { - this.conversionApi.writer = writer; - + _convertAttribute( range, key, oldValue, newValue, conversionApi ) { // Create a list with attributes to consume. - this.conversionApi.consumable = this._createConsumableForRange( range, `attribute:${ key }` ); + this._addConsumablesForRange( conversionApi.consumable, range, `attribute:${ key }` ); // Create a separate attribute event for each node in the range. for ( const value of range ) { @@ -238,10 +259,8 @@ export default class DowncastDispatcher { attributeNewValue: newValue }; - this._testAndFire( `attribute:${ key }`, data ); + this._testAndFire( `attribute:${ key }`, data, conversionApi ); } - - this._clearConversionApi(); } /** @@ -258,25 +277,22 @@ export default class DowncastDispatcher { * Element reconversion is defined by the `triggerBy` configuration for the * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} conversion helper. * + * @protected * @fires insert * @fires attribute * @param {module:engine/model/range~Range} range The range to reinsert. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ - convertReinsert( range, writer ) { - this.conversionApi.writer = writer; - + _convertReinsert( range, conversionApi ) { // Create a list of things that can be consumed, consisting of nodes and their attributes. - this.conversionApi.consumable = this._createInsertConsumable( range ); + this._addConsumablesForInsert( conversionApi.consumable, range ); // Convert the elements - without converting children. // // Fire a separate insert event for each node and text fragment contained in the range. for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( { ...data, reconversion: true } ); + this._convertInsertWithAttributes( { ...data, reconversion: true }, conversionApi ); } - - this._clearConversionApi(); } /** @@ -294,21 +310,20 @@ export default class DowncastDispatcher { convertSelection( selection, markers, writer ) { const markersAtSelection = Array.from( markers.getMarkersAtPosition( selection.getFirstPosition() ) ); - this.conversionApi.writer = writer; - this.conversionApi.consumable = this._createSelectionConsumable( selection, markersAtSelection ); + const conversionApi = this._prepareConversionApi( writer ); - this.fire( 'selection', { selection }, this.conversionApi ); + this._addConsumablesForSelection( conversionApi.consumable, selection, markersAtSelection ); - if ( !selection.isCollapsed ) { - this._clearConversionApi(); + this.fire( 'selection', { selection }, conversionApi ); + if ( !selection.isCollapsed ) { return; } for ( const marker of markersAtSelection ) { const markerRange = marker.getRange(); - if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, this.conversionApi.mapper ) ) { + if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, conversionApi.mapper ) ) { continue; } @@ -318,8 +333,8 @@ export default class DowncastDispatcher { markerRange }; - if ( this.conversionApi.consumable.test( selection, 'addMarker:' + marker.name ) ) { - this.fire( 'addMarker:' + marker.name, data, this.conversionApi ); + if ( conversionApi.consumable.test( selection, 'addMarker:' + marker.name ) ) { + this.fire( 'addMarker:' + marker.name, data, conversionApi ); } } @@ -333,103 +348,78 @@ export default class DowncastDispatcher { }; // Do not fire event if the attribute has been consumed. - if ( this.conversionApi.consumable.test( selection, 'attribute:' + data.attributeKey ) ) { - this.fire( 'attribute:' + data.attributeKey + ':$text', data, this.conversionApi ); + if ( conversionApi.consumable.test( selection, 'attribute:' + data.attributeKey ) ) { + this.fire( 'attribute:' + data.attributeKey + ':$text', data, conversionApi ); } } - - this._clearConversionApi(); } /** * Converts the added marker. Fires the {@link #event:addMarker `addMarker`} event for each item * in the marker's range. If the range is collapsed, a single event is dispatched. See the event description for more details. * + * @protected * @fires addMarker * @param {String} markerName Marker name. * @param {module:engine/model/range~Range} markerRange The marker range. * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. */ - convertMarkerAdd( markerName, markerRange, writer ) { + _convertMarkerAdd( markerName, markerRange, conversionApi ) { // Do not convert if range is in graveyard. if ( markerRange.root.rootName == '$graveyard' ) { return; } - this.conversionApi.writer = writer; - // In markers' case, event name == consumable name. const eventName = 'addMarker:' + markerName; // // First, fire an event for the whole marker. // - const consumable = new Consumable(); - consumable.add( markerRange, eventName ); - - this.conversionApi.consumable = consumable; + conversionApi.consumable.add( markerRange, eventName ); - this.fire( eventName, { markerName, markerRange }, this.conversionApi ); + this.fire( eventName, { markerName, markerRange }, conversionApi ); // // Do not fire events for each item inside the range if the range got consumed. // - if ( !consumable.test( markerRange, eventName ) ) { - this._clearConversionApi(); - + if ( !conversionApi.consumable.test( markerRange, eventName ) ) { return; } // // Then, fire an event for each item inside the marker range. // - this.conversionApi.consumable = this._createConsumableForRange( markerRange, eventName ); + this._addConsumablesForRange( conversionApi.consumable, markerRange, eventName ); for ( const item of markerRange.getItems() ) { // Do not fire event for already consumed items. - if ( !this.conversionApi.consumable.test( item, eventName ) ) { + if ( !conversionApi.consumable.test( item, eventName ) ) { continue; } const data = { item, range: Range._createOn( item ), markerName, markerRange }; - this.fire( eventName, data, this.conversionApi ); + this.fire( eventName, data, conversionApi ); } - - this._clearConversionApi(); } /** * Fires the conversion of the marker removal. Fires the {@link #event:removeMarker `removeMarker`} event with the provided data. * + * @protected * @fires removeMarker * @param {String} markerName Marker name. * @param {module:engine/model/range~Range} markerRange The marker range. * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. */ - convertMarkerRemove( markerName, markerRange, writer ) { + _convertMarkerRemove( markerName, markerRange, conversionApi ) { // Do not convert if range is in graveyard. if ( markerRange.root.rootName == '$graveyard' ) { return; } - this.conversionApi.writer = writer; - - this.fire( 'removeMarker:' + markerName, { markerName, markerRange }, this.conversionApi ); - - this._clearConversionApi(); - } - - /** - * TODO maybe this should be sth like convertNestedInsert to indicate that it's inside the current outer conversion block? - * - * @protected - */ - _convertInsert( range ) { - // Fire a separate insert event for each node and text fragment contained in the range. - for ( const data of Array.from( range ).map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( data ); - } + this.fire( 'removeMarker:' + markerName, { markerName, markerRange }, conversionApi ); } /** @@ -455,9 +445,7 @@ export default class DowncastDispatcher { * @param {module:engine/model/range~Range} range The inserted range. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ - _createInsertConsumable( range ) { - const consumable = new Consumable(); - + _addConsumablesForInsert( consumable, range ) { for ( const value of range ) { const item = value.item; @@ -479,9 +467,7 @@ export default class DowncastDispatcher { * @param {String} type Consumable type. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ - _createConsumableForRange( range, type ) { - const consumable = new Consumable(); - + _addConsumablesForRange( consumable, range, type ) { for ( const item of range.getItems() ) { consumable.add( item, type ); } @@ -497,9 +483,7 @@ export default class DowncastDispatcher { * @param {Iterable.} markers Markers that contain the selection. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ - _createSelectionConsumable( selection, markers ) { - const consumable = new Consumable(); - + _addConsumablesForSelection( consumable, selection, markers ) { consumable.add( selection, 'selection' ); for ( const marker of markers ) { @@ -522,23 +506,30 @@ export default class DowncastDispatcher { * @param {String} type Event type. * @param {Object} data Event data. */ - _testAndFire( type, data ) { - if ( !this.conversionApi.consumable.test( data.item, type ) ) { + _testAndFire( type, data, conversionApi ) { + if ( !conversionApi.consumable.test( data.item, type ) ) { // Do not fire event if the item was consumed. return; } - this.fire( getEventName( type, data ), data, this.conversionApi ); + this.fire( getEventName( type, data ), data, conversionApi ); } /** - * Clears the conversion API object. + * TODO * * @private */ - _clearConversionApi() { - delete this.conversionApi.writer; - delete this.conversionApi.consumable; + _prepareConversionApi( writer, options = {} ) { + const conversionApi = { + ...this.conversionApi, + consumable: new Consumable(), + writer, + options, + convertInsert: range => this._convertInsert( range, conversionApi ) + }; + + return conversionApi; } /** @@ -549,8 +540,8 @@ export default class DowncastDispatcher { * @fires attribute * @param {Object} data Event data. */ - _convertInsertWithAttributes( data ) { - this._testAndFire( 'insert', data ); + _convertInsertWithAttributes( data, conversionApi ) { + this._testAndFire( 'insert', data, conversionApi ); // Fire a separate addAttribute event for each attribute that was set on inserted items. // This is important because most attributes converters will listen only to add/change/removeAttribute events. @@ -560,7 +551,7 @@ export default class DowncastDispatcher { data.attributeOldValue = null; data.attributeNewValue = data.item.getAttribute( key ); - this._testAndFire( `attribute:${ key }`, data ); + this._testAndFire( `attribute:${ key }`, data, conversionApi ); } } diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 500a1845c56..ca9357ca2ab 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1928,9 +1928,8 @@ function reinsertNodes( viewElement, nodes, conversionApi, reconversion ) { ); } } else { - // TODO this should be exposed by conversionApi? // Note that this is not creating another consumable, it's using the current one. - conversionApi.dispatcher._convertInsert( ModelRange._createOn( modelChildNode ) ); + conversionApi.convertInsert( ModelRange._createOn( modelChildNode ) ); } } } diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js index bd4aa51879d..f9b18a04abc 100644 --- a/packages/ckeditor5-engine/src/dev-utils/model.js +++ b/packages/ckeditor5-engine/src/dev-utils/model.js @@ -260,28 +260,28 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu return writer.createUIElement( name ); } ) ); + const markerEntries = []; + + if ( markers ) { + // To provide stable results, sort markers by name. + for ( const marker of Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ) ) { + markerEntries.push( [ marker.name, marker.getRange() ] ); + } + } + // Convert model to view. const writer = view._writer; - downcastDispatcher.convertInsert( range, writer ); + downcastDispatcher.convertInsert( range, markerEntries, writer ); // Convert model selection to view selection. if ( selection ) { downcastDispatcher.convertSelection( selection, markers || model.markers, writer ); } - if ( markers ) { - // To provide stable results, sort markers by name. - markers = Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ); - - for ( const marker of markers ) { - downcastDispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer ); - } - } - // Parse view to data string. let data = viewStringify( viewRoot, viewDocument.selection, { sameSelectionCharacters: true } ); - // Removing unneccessary
and
added because `viewRoot` was also stringified alongside input data. + // Removing unnecessary
and
added because `viewRoot` was also stringified alongside input data. data = data.substr( 5, data.length - 11 ); view.destroy(); diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 832911952bd..2bfb8a93231 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -44,8 +44,8 @@ describe( 'DowncastDispatcher', () => { } ); describe( 'convertChanges', () => { - it( 'should call convertInsert for insert change', () => { - sinon.stub( dispatcher, 'convertInsert' ); + it( 'should call _convertInsert for insert change', () => { + sinon.stub( dispatcher, '_convertInsert' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); @@ -56,15 +56,15 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertInsert.calledOnce ).to.be.true; - expect( dispatcher.convertInsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true; + expect( dispatcher._convertInsert.calledOnce ).to.be.true; + expect( dispatcher._convertInsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true; expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it( 'should call convertRemove for remove change', () => { - sinon.stub( dispatcher, 'convertRemove' ); + it( 'should call _convertRemove for remove change', () => { + sinon.stub( dispatcher, '_convertRemove' ); const position = model.createPositionFromPath( root, [ 0 ] ); @@ -74,15 +74,14 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertRemove.calledWith( position, 2, '$text' ) ).to.be.true; + expect( dispatcher._convertRemove.calledWith( position, 2, '$text' ) ).to.be.true; expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it.skip( 'should call convertAttribute for attribute change', () => { - sinon.stub( dispatcher, 'convertAttribute' ); - sinon.stub( dispatcher, '_mapChangesWithAutomaticReconversion' ).callsFake( differ => differ.getChanges() ); + it.skip( 'should call _convertAttribute for attribute change', () => { + sinon.stub( dispatcher, '_convertAttribute' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); @@ -95,17 +94,16 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertAttribute.calledWith( range, 'key', null, 'foo' ) ).to.be.true; + expect( dispatcher._convertAttribute.calledWith( range, 'key', null, 'foo' ) ).to.be.true; expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); it.skip( 'should handle multiple changes', () => { - sinon.stub( dispatcher, 'convertInsert' ); - sinon.stub( dispatcher, 'convertRemove' ); - sinon.stub( dispatcher, 'convertAttribute' ); - sinon.stub( dispatcher, '_mapChangesWithAutomaticReconversion' ).callsFake( differ => differ.getChanges() ); + sinon.stub( dispatcher, '_convertInsert' ); + sinon.stub( dispatcher, '_convertRemove' ); + sinon.stub( dispatcher, '_convertAttribute' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); @@ -121,16 +119,16 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertInsert.calledTwice ).to.be.true; - expect( dispatcher.convertRemove.calledOnce ).to.be.true; - expect( dispatcher.convertAttribute.calledOnce ).to.be.true; + expect( dispatcher._convertInsert.calledTwice ).to.be.true; + expect( dispatcher._convertRemove.calledOnce ).to.be.true; + expect( dispatcher._convertAttribute.calledOnce ).to.be.true; expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it( 'should call convertMarkerAdd when markers are added', () => { - sinon.stub( dispatcher, 'convertMarkerAdd' ); + it( 'should call _convertMarkerAdd when markers are added', () => { + sinon.stub( dispatcher, '_convertMarkerAdd' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -144,15 +142,15 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertMarkerAdd.calledWith( 'foo', fooRange ) ); - expect( dispatcher.convertMarkerAdd.calledWith( 'bar', barRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); - it( 'should call convertMarkerRemove when markers are removed', () => { - sinon.stub( dispatcher, 'convertMarkerRemove' ); + it( 'should call _convertMarkerRemove when markers are removed', () => { + sinon.stub( dispatcher, '_convertMarkerRemove' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -166,16 +164,16 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertMarkerRemove.calledWith( 'foo', fooRange ) ); - expect( dispatcher.convertMarkerRemove.calledWith( 'bar', barRange ) ); + expect( dispatcher._convertMarkerRemove.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerRemove.calledWith( 'bar', barRange ) ); expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; } ); it( 'should re-render markers which view elements got unbound during conversion', () => { - sinon.stub( dispatcher, 'convertMarkerRemove' ); - sinon.stub( dispatcher, 'convertMarkerAdd' ); + sinon.stub( dispatcher, '_convertMarkerRemove' ); + sinon.stub( dispatcher, '_convertMarkerAdd' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -190,10 +188,10 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertChanges( differStub, model.markers, writer ); } ); - expect( dispatcher.convertMarkerRemove.calledWith( 'foo', fooRange ) ); - expect( dispatcher.convertMarkerRemove.calledWith( 'bar', barRange ) ); - expect( dispatcher.convertMarkerAdd.calledWith( 'foo', fooRange ) ); - expect( dispatcher.convertMarkerAdd.calledWith( 'bar', barRange ) ); + expect( dispatcher._convertMarkerRemove.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerRemove.calledWith( 'bar', barRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; @@ -240,7 +238,7 @@ describe( 'DowncastDispatcher', () => { expect( conversionApi.consumable.consume( data.item, 'attribute:' + key ) ).to.be.true; } ); - dispatcher.convertInsert( range ); + dispatcher.convertInsert( range, [] ); // Check the data passed to called events and the order of them. expect( loggedEvents ).to.deep.equal( [ @@ -274,7 +272,7 @@ describe( 'DowncastDispatcher', () => { const range = model.createRangeIn( root ); - dispatcher.convertInsert( range ); + dispatcher.convertInsert( range, [] ); expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; @@ -298,7 +296,7 @@ describe( 'DowncastDispatcher', () => { loggedEvents.push( log ); } ); - dispatcher.convertRemove( model.createPositionAt( root, 3 ), 3, '$text' ); + dispatcher._convertRemove( model.createPositionAt( root, 3 ), 3, '$text' ); expect( loggedEvents ).to.deep.equal( [ 'remove:3:3' ] ); @@ -582,7 +580,7 @@ describe( 'DowncastDispatcher', () => { expect( data.markerRange.isEqual( range ) ).to.be.true; } ); - dispatcher.convertMarkerAdd( 'name', range ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); expect( spy.calledOnce ).to.be.true; @@ -596,7 +594,7 @@ describe( 'DowncastDispatcher', () => { const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher.convertMarkerAdd( 'name', eleRange ); + dispatcher._convertMarkerAdd( 'name', eleRange, dispatcher._prepareConversionApi() ); expect( dispatcher.fire.called ).to.be.true; @@ -608,7 +606,7 @@ describe( 'DowncastDispatcher', () => { const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher.convertMarkerAdd( 'name', gyRange ); + dispatcher._convertMarkerAdd( 'name', gyRange, dispatcher._prepareConversionApi() ); expect( dispatcher.fire.called ).to.be.false; @@ -645,7 +643,7 @@ describe( 'DowncastDispatcher', () => { } } ); - dispatcher.convertMarkerAdd( 'name', range ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); expect( spyWholeRange.calledOnce ).to.be.true; expect( spyItems.calledTwice ).to.be.true; @@ -674,7 +672,7 @@ describe( 'DowncastDispatcher', () => { } } ); - dispatcher.convertMarkerAdd( 'name', range ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); expect( spyItems.called ).to.be.false; @@ -702,7 +700,7 @@ describe( 'DowncastDispatcher', () => { } }, { priority: 'high' } ); - dispatcher.convertMarkerAdd( 'marker', range ); + dispatcher._convertMarkerAdd( 'marker', range, dispatcher._prepareConversionApi() ); expect( addMarkerSpy.called ).to.be.false; expect( highAddMarkerSpy.calledOnce ).to.be.true; @@ -731,7 +729,7 @@ describe( 'DowncastDispatcher', () => { } }, { priority: 'high' } ); - dispatcher.convertMarkerAdd( 'marker', range ); + dispatcher._convertMarkerAdd( 'marker', range, dispatcher._prepareConversionApi() ); expect( addMarkerSpy.called ).to.be.false; @@ -757,7 +755,7 @@ describe( 'DowncastDispatcher', () => { it( 'should fire removeMarker event', () => { sinon.spy( dispatcher, 'fire' ); - dispatcher.convertMarkerRemove( 'name', range ); + dispatcher._convertMarkerRemove( 'name', range, dispatcher._prepareConversionApi() ); expect( dispatcher.fire.calledWith( 'removeMarker:name' ) ).to.be.true; @@ -769,7 +767,7 @@ describe( 'DowncastDispatcher', () => { const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher.convertMarkerRemove( 'name', gyRange ); + dispatcher._convertMarkerRemove( 'name', gyRange, dispatcher._prepareConversionApi() ); expect( dispatcher.fire.called ).to.be.false; @@ -783,7 +781,7 @@ describe( 'DowncastDispatcher', () => { const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher.convertMarkerRemove( 'name', eleRange ); + dispatcher._convertMarkerRemove( 'name', eleRange, dispatcher._prepareConversionApi() ); expect( dispatcher.fire.called ).to.be.true; @@ -799,7 +797,7 @@ describe( 'DowncastDispatcher', () => { expect( data.markerRange.isEqual( range ) ).to.be.true; } ); - dispatcher.convertMarkerRemove( 'name', range ); + dispatcher._convertMarkerRemove( 'name', range, dispatcher._prepareConversionApi() ); expect( dispatcher.conversionApi.writer ).to.be.undefined; expect( dispatcher.conversionApi.consumable ).to.be.undefined; @@ -819,7 +817,7 @@ describe( 'DowncastDispatcher', () => { evt.stop(); }, { priority: 'high' } ); - dispatcher.convertMarkerRemove( 'marker', range ); + dispatcher._convertMarkerRemove( 'marker', range, dispatcher._prepareConversionApi() ); expect( removeMarkerSpy.called ).to.be.false; expect( highRemoveMarkerSpy.calledOnce ).to.be.true; diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 10959114a77..4646c633b8b 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -117,7 +117,7 @@ describe( 'DowncastHelpers', () => { expectResult( '

' ); } ); - describe( 'config.triggerBy', () => { + describe.skip( 'config.triggerBy', () => { describe( 'with simple block view structure (without children)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { @@ -3786,8 +3786,9 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); - dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer ); + const markers = [ [ marker.name, marker.getRange() ] ]; + + dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3812,8 +3813,9 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); - dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer ); + const markers = [ [ marker.name, marker.getRange() ] ]; + + dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3841,8 +3843,9 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); - dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer ); + const markers = [ [ marker.name, marker.getRange() ] ]; + + dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3870,8 +3873,9 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); - dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer ); + const markers = [ [ marker.name, marker.getRange() ] ]; + + dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3916,7 +3920,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); + dispatcher.convertInsert( model.createRangeIn( modelRoot ), [], writer ); // Add ui element to view. const uiElement = new ViewUIElement( viewDocument, 'span' ); @@ -3941,7 +3945,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); + dispatcher.convertInsert( model.createRangeIn( modelRoot ), [], writer ); // Add ui element to view. const uiElement = new ViewUIElement( viewDocument, 'span' ); @@ -4222,7 +4226,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), writer ); + dispatcher.convertInsert( model.createRangeIn( modelRoot ), model.markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); diff --git a/packages/ckeditor5-heading/src/title.js b/packages/ckeditor5-heading/src/title.js index 9f176c7a8d4..d9e6cdf6a73 100644 --- a/packages/ckeditor5-heading/src/title.js +++ b/packages/ckeditor5-heading/src/title.js @@ -153,27 +153,24 @@ export default class Title extends Plugin { const rootRange = model.createRangeIn( root ); const viewDocumentFragment = viewWriter.createDocumentFragment(); - data.downcastDispatcher.conversionApi.options = options; - - // Convert the entire root to view. - data.mapper.clearBindings(); - data.mapper.bindElements( root, viewDocumentFragment ); - data.downcastDispatcher.convertInsert( rootRange, viewWriter ); - - // Convert all markers that intersects with body. + // Find all markers that intersects with body. const bodyStartPosition = model.createPositionAfter( root.getChild( 0 ) ); const bodyRange = model.createRange( bodyStartPosition, model.createPositionAt( root, 'end' ) ); + const markers = []; + for ( const marker of model.markers ) { const intersection = bodyRange.getIntersection( marker.getRange() ); if ( intersection ) { - data.downcastDispatcher.convertMarkerAdd( marker.name, intersection, viewWriter ); + markers.push( [ marker.name, intersection ] ); } } - // Clean `conversionApi`. - delete data.downcastDispatcher.conversionApi.options; + // Convert the entire root to view. + data.mapper.clearBindings(); + data.mapper.bindElements( root, viewDocumentFragment ); + data.downcastDispatcher.convertInsert( rootRange, markers, viewWriter, options ); // Remove title element from view. viewWriter.remove( viewWriter.createRangeOn( viewDocumentFragment.getChild( 0 ) ) ); From cf242d59f1236b3d21752f8ea07c54d7a426f52b Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 12 Aug 2021 16:01:59 +0200 Subject: [PATCH 009/140] Code cleanup. --- .../src/conversion/downcastdispatcher.js | 147 +++++++++--------- 1 file changed, 77 insertions(+), 70 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 69aa3adcde0..352db1f58df 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -117,7 +117,7 @@ export default class DowncastDispatcher { */ constructor( conversionApi ) { /** - * An interface passed by the dispatcher to the event callbacks. + * A template for an interface passed by the dispatcher to the event callbacks. * * @private * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi} @@ -176,14 +176,13 @@ export default class DowncastDispatcher { /** * Starts a conversion of a range insertion. * - * For each node in the range, {@link #event:insert `insert` event is fired}. For each attribute on each node, - * {@link #event:attribute `attribute` event is fired}. - * * @fires insert * @fires attribute + * @fires addMarker * @param {module:engine/model/range~Range} range The inserted range. + * @param {Array.} markers The list of marker entries `[ name, range ]` that should be down-casted. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. - * @param {Object} [options] TODO + * @param {Object} [options] Optional options object passed to `convertionApi.options`. */ convertInsert( range, markers, writer, options = {} ) { const conversionApi = this._prepareConversionApi( writer, options ); @@ -195,6 +194,65 @@ export default class DowncastDispatcher { } } + /** + * Starts the model selection conversion. + * + * Fires events for a given {@link module:engine/model/selection~Selection selection} to start the selection conversion. + * + * @fires selection + * @fires addMarker + * @fires attribute + * @param {module:engine/model/selection~Selection} selection The selection to convert. + * @param {module:engine/model/markercollection~MarkerCollection} markers Markers connected with the converted model. + * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. + */ + convertSelection( selection, markers, writer ) { + const markersAtSelection = Array.from( markers.getMarkersAtPosition( selection.getFirstPosition() ) ); + + const conversionApi = this._prepareConversionApi( writer ); + + this._addConsumablesForSelection( conversionApi.consumable, selection, markersAtSelection ); + + this.fire( 'selection', { selection }, conversionApi ); + + if ( !selection.isCollapsed ) { + return; + } + + for ( const marker of markersAtSelection ) { + const markerRange = marker.getRange(); + + if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, conversionApi.mapper ) ) { + continue; + } + + const data = { + item: selection, + markerName: marker.name, + markerRange + }; + + if ( conversionApi.consumable.test( selection, 'addMarker:' + marker.name ) ) { + this.fire( 'addMarker:' + marker.name, data, conversionApi ); + } + } + + for ( const key of selection.getAttributeKeys() ) { + const data = { + item: selection, + range: selection.getFirstRange(), + attributeKey: key, + attributeOldValue: null, + attributeNewValue: selection.getAttribute( key ) + }; + + // Do not fire event if the attribute has been consumed. + if ( conversionApi.consumable.test( selection, 'attribute:' + data.attributeKey ) ) { + this.fire( 'attribute:' + data.attributeKey + ':$text', data, conversionApi ); + } + } + } + /** * Starts a conversion of a range insertion. * @@ -205,7 +263,7 @@ export default class DowncastDispatcher { * @fires insert * @fires attribute * @param {module:engine/model/range~Range} range The inserted range. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertInsert( range, conversionApi ) { // Create a list of things that can be consumed, consisting of nodes and their attributes. @@ -224,7 +282,7 @@ export default class DowncastDispatcher { * @param {module:engine/model/position~Position} position Position from which node was removed. * @param {Number} length Offset size of removed node. * @param {String} name Name of removed node. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertRemove( position, length, name, conversionApi ) { this.fire( 'remove:' + name, { position, length }, conversionApi ); @@ -241,7 +299,7 @@ export default class DowncastDispatcher { * @param {String} key Key of the attribute that has changed. * @param {*} oldValue Attribute value before the change or `null` if the attribute has not been set before. * @param {*} newValue New attribute value or `null` if the attribute has been removed. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertAttribute( range, key, oldValue, newValue, conversionApi ) { // Create a list with attributes to consume. @@ -281,7 +339,7 @@ export default class DowncastDispatcher { * @fires insert * @fires attribute * @param {module:engine/model/range~Range} range The range to reinsert. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertReinsert( range, conversionApi ) { // Create a list of things that can be consumed, consisting of nodes and their attributes. @@ -295,65 +353,6 @@ export default class DowncastDispatcher { } } - /** - * Starts the model selection conversion. - * - * Fires events for a given {@link module:engine/model/selection~Selection selection} to start the selection conversion. - * - * @fires selection - * @fires addMarker - * @fires attribute - * @param {module:engine/model/selection~Selection} selection The selection to convert. - * @param {module:engine/model/markercollection~MarkerCollection} markers Markers connected with the converted model. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. - */ - convertSelection( selection, markers, writer ) { - const markersAtSelection = Array.from( markers.getMarkersAtPosition( selection.getFirstPosition() ) ); - - const conversionApi = this._prepareConversionApi( writer ); - - this._addConsumablesForSelection( conversionApi.consumable, selection, markersAtSelection ); - - this.fire( 'selection', { selection }, conversionApi ); - - if ( !selection.isCollapsed ) { - return; - } - - for ( const marker of markersAtSelection ) { - const markerRange = marker.getRange(); - - if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, conversionApi.mapper ) ) { - continue; - } - - const data = { - item: selection, - markerName: marker.name, - markerRange - }; - - if ( conversionApi.consumable.test( selection, 'addMarker:' + marker.name ) ) { - this.fire( 'addMarker:' + marker.name, data, conversionApi ); - } - } - - for ( const key of selection.getAttributeKeys() ) { - const data = { - item: selection, - range: selection.getFirstRange(), - attributeKey: key, - attributeOldValue: null, - attributeNewValue: selection.getAttribute( key ) - }; - - // Do not fire event if the attribute has been consumed. - if ( conversionApi.consumable.test( selection, 'attribute:' + data.attributeKey ) ) { - this.fire( 'attribute:' + data.attributeKey + ':$text', data, conversionApi ); - } - } - } - /** * Converts the added marker. Fires the {@link #event:addMarker `addMarker`} event for each item * in the marker's range. If the range is collapsed, a single event is dispatched. See the event description for more details. @@ -362,7 +361,7 @@ export default class DowncastDispatcher { * @fires addMarker * @param {String} markerName Marker name. * @param {module:engine/model/range~Range} markerRange The marker range. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertMarkerAdd( markerName, markerRange, conversionApi ) { // Do not convert if range is in graveyard. @@ -411,7 +410,7 @@ export default class DowncastDispatcher { * @fires removeMarker * @param {String} markerName Marker name. * @param {module:engine/model/range~Range} markerRange The marker range. - * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertMarkerRemove( markerName, markerRange, conversionApi ) { // Do not convert if range is in graveyard. @@ -442,6 +441,7 @@ export default class DowncastDispatcher { * assuming that the range has just been inserted to the model. * * @private + * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. * @param {module:engine/model/range~Range} range The inserted range. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ @@ -463,6 +463,7 @@ export default class DowncastDispatcher { * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume for a given range. * * @private + * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. * @param {module:engine/model/range~Range} range The affected range. * @param {String} type Consumable type. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. @@ -479,6 +480,7 @@ export default class DowncastDispatcher { * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with selection consumable values. * * @private + * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. * @param {module:engine/model/selection~Selection} selection The selection to create the consumable from. * @param {Iterable.} markers Markers that contain the selection. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. @@ -505,6 +507,7 @@ export default class DowncastDispatcher { * @fires attribute * @param {String} type Event type. * @param {Object} data Event data. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _testAndFire( type, data, conversionApi ) { if ( !conversionApi.consumable.test( data.item, type ) ) { @@ -519,6 +522,9 @@ export default class DowncastDispatcher { * TODO * * @private + * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. + * @param {Object} [options] Optional options passed to `convertionApi.options`. + * @return {module:engine/conversion/downcastdispatcher~DowncastConversionApi} The conversion API object. */ _prepareConversionApi( writer, options = {} ) { const conversionApi = { @@ -539,6 +545,7 @@ export default class DowncastDispatcher { * @fires insert * @fires attribute * @param {Object} data Event data. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertInsertWithAttributes( data, conversionApi ) { this._testAndFire( 'insert', data, conversionApi ); From daae0cf01b38fe2e34a3aeb47365c56cab2d41f4 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 12 Aug 2021 16:04:53 +0200 Subject: [PATCH 010/140] Update html embed downcast conversion. --- packages/ckeditor5-html-embed/src/htmlembedediting.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/ckeditor5-html-embed/src/htmlembedediting.js b/packages/ckeditor5-html-embed/src/htmlembedediting.js index 6c4a421eaec..49eddfeed74 100644 --- a/packages/ckeditor5-html-embed/src/htmlembedediting.js +++ b/packages/ckeditor5-html-embed/src/htmlembedediting.js @@ -119,10 +119,7 @@ export default class HtmlEmbedEditing extends Plugin { } ); editor.conversion.for( 'editingDowncast' ).elementToElement( { - triggerBy: { - attributes: [ 'value' ] - }, - model: 'rawHtml', + model: { name: 'rawHtml', attributes: [ 'value' ] }, view: ( modelElement, { writer } ) => { let domContentWrapper, state, props; From 34f06b37c6fa70b3f41fc63ae001cc0cfc33cce6 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 12 Aug 2021 16:59:10 +0200 Subject: [PATCH 011/140] Fixed marker double conversion. --- packages/ckeditor5-engine/src/conversion/downcastdispatcher.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 352db1f58df..0ec835d03ec 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -381,8 +381,9 @@ export default class DowncastDispatcher { // // Do not fire events for each item inside the range if the range got consumed. + // Also consume the whole marker consumable if it wasn't consumed. // - if ( !conversionApi.consumable.test( markerRange, eventName ) ) { + if ( !conversionApi.consumable.consume( markerRange, eventName ) ) { return; } From 4d90152d189d1c15a348a5d90beb224e24ef71b5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 17 Aug 2021 19:51:14 +0200 Subject: [PATCH 012/140] Code cleaning. Added JSDocs. --- .../src/conversion/downcastdispatcher.js | 70 +++-- .../src/conversion/downcasthelpers.js | 274 ++++++++++++------ .../ckeditor5-engine/src/conversion/mapper.js | 29 +- 3 files changed, 249 insertions(+), 124 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 0ec835d03ec..17b6ca4fd0f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -48,8 +48,9 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * * Additionally, downcast dispatcher fires events for {@link module:engine/model/markercollection~Marker marker} changes: * - * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker} – If a marker was added. - * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:removeMarker} – If a marker was removed. + * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker `addMarker`} – If a marker was added. + * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:removeMarker `removeMarker`} – If a marker was + * removed. * * Note that changing a marker is done through removing the marker from the old range and adding it to the new range, * so both events are fired. @@ -57,11 +58,11 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * Finally, downcast dispatcher also handles firing events for the {@link module:engine/model/selection model selection} * conversion: * - * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:selection} + * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:selection `selection`} * – Converts the selection from the model to the view. - * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute} + * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} * – Fired for every selection attribute. - * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker} + * * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker `addMarker`} * – Fired for every marker that contains a selection. * * Unlike the model tree and the markers, the events for selection are not fired for changes but for a selection state. @@ -170,11 +171,11 @@ export default class DowncastDispatcher { } // Remove mappings for all removed view elements. - conversionApi.mapper.flushTemporaryMappings(); + conversionApi.mapper.flushDeferredBindings(); } /** - * Starts a conversion of a range insertion. + * Starts a conversion of a range and provided markers. * * @fires insert * @fires attribute @@ -254,7 +255,7 @@ export default class DowncastDispatcher { } /** - * Starts a conversion of a range insertion. + * Fires insertion conversion of a range of elements. * * For each node in the range, {@link #event:insert `insert` event is fired}. For each attribute on each node, * {@link #event:attribute `attribute` event is fired}. @@ -322,18 +323,10 @@ export default class DowncastDispatcher { } /** - * TODO + * Fires re-insertion conversion of a range of elements (only elements on the range depth, without children). * - * Starts the reconversion of an element. It will: - * - * * Fire an {@link #event:insert `insert` event} for the element to reconvert. - * * Fire an {@link #event:attribute `attribute` event} for element attributes. - * - * This will not reconvert children of the element if they have existing (already converted) views. For newly inserted child elements - * it will behave the same as {@link #convertInsert}. - * - * Element reconversion is defined by the `triggerBy` configuration for the - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} conversion helper. + * For each node in the range on it's depth (without children), {@link #event:insert `insert` event} is fired. + * For each attribute on each node, {@link #event:attribute `attribute` event} is fired. * * @protected * @fires insert @@ -423,9 +416,14 @@ export default class DowncastDispatcher { } /** - * TODO + * Fires the reduction of changes buffered in the {@link module:engine/model/differ~Differ `Differ`}. + * + * Features can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with `reinsert` entries to trigger + * reconversion. The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * `DowncastHelpers.elementToStructure()`} is using this event to trigger reconversion. * * @private + * @fires reduceChanges * @param {Iterable.} changes * @returns {Iterable.} */ @@ -450,10 +448,13 @@ export default class DowncastDispatcher { for ( const value of range ) { const item = value.item; - consumable.add( item, 'insert' ); + // Add consumable if it wasn't there yet. + if ( consumable.test( item, 'insert' ) === null ) { + consumable.add( item, 'insert' ); - for ( const key of item.getAttributeKeys() ) { - consumable.add( item, 'attribute:' + key ); + for ( const key of item.getAttributeKeys() ) { + consumable.add( item, 'attribute:' + key ); + } } } @@ -520,7 +521,8 @@ export default class DowncastDispatcher { } /** - * TODO + * Builds an instance of the {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi} from a template and a given + * {@link module:engine/view/downcastwriter~DowncastWriter `DowncastWriter`} and options object. * * @private * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. @@ -563,6 +565,19 @@ export default class DowncastDispatcher { } } + /** + * Fired for the reduction of changes buffered in the {@link module:engine/model/differ~Differ `Differ`} before + * {@link #convertChanges `convertChanges()`} will fire any events to convert changes. + * + * Features can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with `reinsert` entries to trigger + * reconversion. The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * `DowncastHelpers.elementToStructure()`} is using this event to trigger reconversion. + * + * @param {Object} data + * @param {Iterable.} data.changes A buffered changes to get reduced. + * @event reduceChanges + */ + /** * Fired for inserted nodes. * @@ -765,6 +780,13 @@ function walkerValueToEventData( value ) { * @member {module:engine/view/downcastwriter~DowncastWriter} #writer */ +/** + * Triggers the nested insert conversion on a specified range. + * + * @method #convertInsert + * @param {module:engine/model/range~Range} range The range to trigger nested insert conversion on. + */ + /** * An object with an additional configuration which can be used during the conversion process. Available only for data downcast conversion. * diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index ca9357ca2ab..df8e7cd7c84 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -573,7 +573,7 @@ export function remove() { // After the range is removed, unbind all view elements from the model. // Range inside view document fragment is used to unbind deeply. for ( const child of conversionApi.writer.createRangeIn( removed ).getItems() ) { - conversionApi.mapper.unbindViewElement( child, false ); + conversionApi.mapper.unbindViewElement( child, { defer: true } ); } }; } @@ -797,8 +797,7 @@ export function wrap( elementCreator ) { * It is expected that the function returns an {@link module:engine/view/element~Element}. * The result of the function will be inserted into the view. * - * The converter automatically consumes the corresponding value from the consumables list, stops the event (see - * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}) and binds the model and view elements. + * The converter automatically consumes the corresponding value from the consumables list and binds the model and view elements. * * downcastDispatcher.on( * 'insert:myElem', @@ -832,30 +831,17 @@ export function insertElement( elementCreator ) { const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); - // Bring back the old view element if it's same as the previous one. - // TODO this is useless if any of the attributes are converted separately - // but helps with replacing table cells that do not need to be replaced (but still useless for col/row span). - if ( data.reconversion ) { - const oldViewElement = conversionApi.mapper.toViewElement( data.item ); - - if ( oldViewElement && oldViewElement.isSimilar( viewElement ) && oldViewElement.root != viewPosition.root ) { - conversionApi.writer.move( conversionApi.writer.createRangeOn( oldViewElement ), viewPosition ); - - return; - } - } - conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - reinsertNodes( viewElement, data.item.getChildren(), conversionApi, data.reconversion ); + reinsertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } /** * TODO */ -function insertStructure( elementCreator ) { +function insertStructure( elementCreator, consumer ) { return ( evt, data, conversionApi ) => { let debugSlots = false; // eslint-disable-line // @if CK_DEBUG_SLOTS // debugSlots = true; @@ -865,25 +851,7 @@ function insertStructure( elementCreator ) { // View creation. const viewElement = elementCreator( data.item, { ...conversionApi, - slotFor( mode ) { - const slot = debugSlots ? - conversionApi.writer.createContainerElement( 'div', { 'data-slot': String( mode ) } ) : - conversionApi.writer.createContainerElement( '$slot' ); - - if ( mode == 'children' ) { - slotsMap.set( slot, Array.from( data.item.getChildren() ) ); - } else if ( typeof mode == 'function' ) { - slotsMap.set( slot, Array.from( data.item.getChildren() ).filter( element => mode( element ) ) ); - } else { - throw new Error( 'unknown slot mode' ); - } - - // TODO throw if the same element is in multiple slots - // TODO all child nodes must be covered here so they won't get converted separately - // TODO Make sure that slots are created for the elements themself or direct descendants and not other elements. - - return slot; - } + slotFor: createSlotFactory( data.item, slotsMap, conversionApi.writer, { debugSlots } ) } ); // Insert the new structure if any was created. Otherwise it's removed. @@ -891,56 +859,24 @@ function insertStructure( elementCreator ) { return; } - if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { + // Check if all children are covered by slots and there is no child that landed in multiple slots. + validateSlotsChildren( data.item, slotsMap ); + + // Consume an element insertion and all present attributes that are specified as a reconversion triggers. + if ( !consumer( data.item, conversionApi.consumable ) ) { return; } - // TODO consume attributes that were watched to trigger this conversion - const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - // Fill slots. - - conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); - - let currentSlot = null; - - // Fill slots with nested view nodes. - for ( const [ slot, nodes ] of slotsMap ) { - currentSlot = slot; - - reinsertNodes( viewElement, nodes, conversionApi, data.reconversion ); - - if ( !debugSlots ) { - conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); - conversionApi.writer.remove( slot ); - } - } - - conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping ); - - function toViewPositionMapping( evt, data ) { - if ( data.viewPosition || !currentSlot ) { - return; - } - - const nodeAfter = data.modelPosition.nodeAfter; - - if ( !nodeAfter ) { - return; - } - - const index = slotsMap.get( currentSlot ).indexOf( nodeAfter ); - - if ( index < 0 ) { - return; - } - - data.viewPosition = data.mapper.findPositionIn( currentSlot, index ); - } + // Fill view slots with previous view elements or create new ones. + fillSlots( viewElement, slotsMap, conversionApi, { + reconversion: data.reconversion, + debugSlots + } ); }; } @@ -1522,7 +1458,16 @@ function downcastElementToElement( config ) { }; } -// TODO +// Model element to view structure conversion helper. +// +// See {@link ~DowncastHelpers#elementToStructure `.elementToStructure()` downcast helper} for examples and config params description. +// +// @param {Object} config Conversion configuration. +// @param {String|Object} config.model +// @param {Array.} [config.model.attributes] +// @param {Boolean} [config.model.children] +// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view +// @returns {Function} Conversion helper. function downcastElementToStructure( config ) { config = cloneDeep( config ); @@ -1530,7 +1475,11 @@ function downcastElementToStructure( config ) { config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { - dispatcher.on( 'insert:' + config.model.name, insertStructure( config.view ), { priority: config.converterPriority || 'normal' } ); + dispatcher.on( + 'insert:' + config.model.name, + insertStructure( config.view, createConsumer( config.model ) ), + { priority: config.converterPriority || 'normal' } + ); if ( config.model.children || config.model.attributes.length ) { dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); @@ -1682,7 +1631,13 @@ function downcastMarkerToHighlight( config ) { }; } -// TODO +// Takes `config.model`, and converts it to an object with normalized structure. +// +// @param {String|Object} model Model configuration or element name. +// @param {String} model.name +// @param {Array.} [model.attributes] +// @param {Boolean} [model.children] +// @returns {Object} function normalizeModelElementConfig( model ) { if ( typeof model == 'string' ) { model = { name: model }; @@ -1830,7 +1785,13 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) { return descriptor; } -// TODO +// Creates a function that checks a single differ diff item whether it should trigger reconversion. +// +// @param {Object} model A normalized `config.model` converter configuration. +// @param {String} model.name The name of element. +// @param {Array.} model.attributes The list of attribute names that should trigger reconversion. +// @param {Boolean} [model.children] Whether the child list change should trigger reconversion. +// @returns {Function} function createChangeReducerCallback( model ) { return ( node, change ) => { if ( !node.is( 'element', model.name ) ) { @@ -1851,7 +1812,14 @@ function createChangeReducerCallback( model ) { }; } -// TODO +// Creates a `reduceChanges` event handler for reconversion. +// +// @param {Object} model A normalized `config.model` converter configuration. +// @param {String} model.name The name of element. +// @param {Array.} model.attributes The list of attribute names that should trigger reconversion. +// @param {Boolean} [model.children] Whether the child list change should trigger reconversion. +// @param {Function} The callback for checking a single diff item whether it should trigger reconversion. +// @returns {Function} function createChangeReducer( model, callback = createChangeReducerCallback( model ) ) { return ( evt, data ) => { const reducedChanges = []; @@ -1914,13 +1882,143 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod }; } -// TODO -function reinsertNodes( viewElement, nodes, conversionApi, reconversion ) { +// Creates a function that checks if an element and it's watched attributes can be consumed and consumes them. +// +// @param {Object} model A normalized `config.model` converter configuration. +// @param {String} model.name The name of element. +// @param {Array.} model.attributes The list of attribute names that should trigger reconversion. +// @param {Boolean} [model.children] Whether the child list change should trigger reconversion. +// @returns {Function} +function createConsumer( model ) { + return ( node, consumable ) => { + const events = [ 'insert' ]; + + // Collect all set attributes that are triggering conversion. + for ( const attributeName of model.attributes ) { + if ( node.hasAttribute( attributeName ) ) { + events.push( `attribute:${ attributeName }` ); + } + } + + if ( !events.every( event => consumable.test( node, event ) ) ) { + return false; + } + + return events.every( event => consumable.consume( node, event ) ); + }; +} + +// Creates a function that create view slots. +// +// @param {module:engine/model/element~Element} element +// @param {Map.>} slotsMap +// @param {module:engine/view/downcastwriter~DowncastWriter} writer +// @param {Object} options +// @param {Boolean} [options.debugSlots] +// @returns {Function} +function createSlotFactory( element, slotsMap, writer, options ) { + return modeOrFilter => { + const slot = options.debugSlots ? + writer.createContainerElement( 'div', { 'data-slot': String( modeOrFilter ) } ) : + writer.createContainerElement( '$slot' ); + + let children = null; + + if ( modeOrFilter === 'children' ) { + children = Array.from( element.getChildren() ); + } else if ( typeof modeOrFilter == 'function' ) { + children = Array.from( element.getChildren() ).filter( element => modeOrFilter( element ) ); + } else { + throw new Error( 'unknown slot mode' ); // TODO + } + + slotsMap.set( slot, children ); + + return slot; + }; +} + +// Checks if all children are covered by slots and there is no child that landed in multiple slots. +// +// @param {module:engine/model/element~Element} +// @param {Map.>} slotsMap +function validateSlotsChildren( element, slotsMap ) { + const childrenInSlots = Array.from( slotsMap.values() ).flat(); + const uniqueChildrenInSlots = new Set( childrenInSlots ); + + if ( uniqueChildrenInSlots.size != childrenInSlots.length ) { + throw new Error( 'same child in multiple slots' ); + } + + if ( uniqueChildrenInSlots.size != element.childCount ) { + throw new Error( 'not all children covered by slots' ); + } +} + +// Fill slots with appropriate view elements. +// +// @param {module:engine/view/element~Element} viewElement +// @param {Map.>} slotsMap +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @param {Object} options +// @param {Boolean} [options.reconversion] +// @param {Boolean} [options.debugSlots] +function fillSlots( viewElement, slotsMap, conversionApi, options ) { + // Set temporary position mapping to redirect child view elements into a proper slots. + conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); + + let currentSlot = null; + + // Fill slots with nested view nodes. + for ( const [ slot, nodes ] of slotsMap ) { + currentSlot = slot; + + reinsertNodes( viewElement, nodes, conversionApi, options ); + + if ( !options.debugSlots ) { + conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); + conversionApi.writer.remove( slot ); + } + } + + conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping ); + + function toViewPositionMapping( evt, data ) { + if ( data.viewPosition || !currentSlot ) { + return; + } + + const nodeAfter = data.modelPosition.nodeAfter; + + if ( !nodeAfter ) { + return; + } + + // Find the proper offset within the slot. + const index = slotsMap.get( currentSlot ).indexOf( nodeAfter ); + + if ( index < 0 ) { + return; + } + + data.viewPosition = data.mapper.findPositionIn( currentSlot, index ); + } +} + +// Inserts view representation of `nodes` into the `viewElement` either by bringing back just removed view nodes +// or by triggering conversion for them. +// +// @param {module:engine/view/element~Element} viewElement +// @param {Iterable.} modelNodes +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @param {Object} options +// @param {Boolean} [options.reconversion] +function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { // Fill with nested view nodes. - for ( const modelChildNode of nodes ) { + for ( const modelChildNode of modelNodes ) { const viewChildNode = conversionApi.mapper.toViewElement( modelChildNode ); - if ( reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { + if ( options.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { conversionApi.writer.move( conversionApi.writer.createRangeOn( viewChildNode ), diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 923d2bc0510..fb6b6c0169b 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -89,10 +89,12 @@ export default class Mapper { this._elementToMarkerNames = new Map(); /** - * TODO + * Map of removed view elements with their current root (used for deferred unbinding). + * * @private + * @member {Map.} */ - this._removedViewElements = new Set(); + this._deferredBindingRemovals = new Map(); /** * Stores marker names of markers which has changed due to unbinding a view element (so it is assumed that the view element @@ -152,8 +154,11 @@ export default class Mapper { * when the previously bound view element is unbound. * * @param {module:engine/view/element~Element} viewElement View element to unbind. + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.defer=false] Controls whether binding should be immediately removed or deferred until the + * {@link #flushDeferredBindings `flushDeferredBindings()`} call. */ - unbindViewElement( viewElement, immediate = true ) { + unbindViewElement( viewElement, options = {} ) { const modelElement = this.toModelElement( viewElement ); if ( this._elementToMarkerNames.has( viewElement ) ) { @@ -162,14 +167,14 @@ export default class Mapper { } } - if ( immediate ) { + if ( options.defer ) { + this._deferredBindingRemovals.set( viewElement, viewElement.root ); + } else { this._viewToModelMapping.delete( viewElement ); if ( this._modelToViewMapping.get( modelElement ) == viewElement ) { this._modelToViewMapping.delete( modelElement ); } - } else { - this._removedViewElements.add( [ viewElement, viewElement.root ] ); } } @@ -255,17 +260,17 @@ export default class Mapper { } /** - * TODO + * Unbinds all deferred binding removals that were not re-attached to some root or document fragment. */ - flushTemporaryMappings() { - for ( const [ viewElement, root ] of this._removedViewElements ) { + flushDeferredBindings() { + for ( const [ viewElement, root ] of this._deferredBindingRemovals ) { // Unbind it only if it wasn't re-attached to some root or document fragment. if ( viewElement.root == root ) { - this.unbindViewElement( viewElement, true ); + this.unbindViewElement( viewElement ); } } - this._removedViewElements = new Set(); + this._deferredBindingRemovals = new Map(); } /** @@ -277,7 +282,7 @@ export default class Mapper { this._markerNameToElements = new Map(); this._elementToMarkerNames = new Map(); this._unboundMarkerNames = new Set(); - this._removedViewElements = new Set(); + this._deferredBindingRemovals = new Map(); } /** From fa34826a6dc6ae454e7c965c134a56fd2abd80f6 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 19 Aug 2021 19:17:42 +0200 Subject: [PATCH 013/140] Adding code comments. --- .../src/conversion/downcasthelpers.js | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index df8e7cd7c84..ab50578db3b 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -839,9 +839,19 @@ export function insertElement( elementCreator ) { } /** - * TODO - */ -function insertStructure( elementCreator, consumer ) { + * Function factory that creates a converter which converts a single model node insertion to a view structure. + * + * It is expected that the passed element creator function returns an {@link module:engine/view/element~Element} with attached slots + * created with `conversionApi.slotFor()` to indicate where child nodes should be converted. + * + * @protected + * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} elementCreator Function returning a view structure, + * which will be inserted. + * @param {module:engine/conversion/downcasthelpers~ConsumerFunction} consumer A callback that is expected to consume all the consumables + * that were used by the element creator. + * @returns {Function} Insert element event converter. +*/ +export function insertStructure( elementCreator, consumer ) { return ( evt, data, conversionApi ) => { let debugSlots = false; // eslint-disable-line // @if CK_DEBUG_SLOTS // debugSlots = true; @@ -854,7 +864,6 @@ function insertStructure( elementCreator, consumer ) { slotFor: createSlotFactory( data.item, slotsMap, conversionApi.writer, { debugSlots } ) } ); - // Insert the new structure if any was created. Otherwise it's removed. if ( !viewElement ) { return; } @@ -1191,10 +1200,7 @@ function changeAttribute( attributeCreator ) { * * @error conversion-attribute-to-attribute-on-text */ - throw new CKEditorError( - 'conversion-attribute-to-attribute-on-text', - [ data, conversionApi ] - ); + throw new CKEditorError( 'conversion-attribute-to-attribute-on-text', this, data ); } // First remove the old attribute if there was one. @@ -1929,7 +1935,12 @@ function createSlotFactory( element, slotsMap, writer, options ) { } else if ( typeof modeOrFilter == 'function' ) { children = Array.from( element.getChildren() ).filter( element => modeOrFilter( element ) ); } else { - throw new Error( 'unknown slot mode' ); // TODO + /** + * Unknown slot mode was provided to `conversionApi.slotFor()` in downcast converter. + * + * @error conversion-slot-mode-unknown + */ + throw new CKEditorError( 'conversion-slot-mode-unknown', this, { modeOrFilter } ); } slotsMap.set( slot, children ); @@ -1947,11 +1958,21 @@ function validateSlotsChildren( element, slotsMap ) { const uniqueChildrenInSlots = new Set( childrenInSlots ); if ( uniqueChildrenInSlots.size != childrenInSlots.length ) { - throw new Error( 'same child in multiple slots' ); + /** + * A filter provided to `conversionApi.slotFor()` is to permissive and leads to downcasting a same node to a multiple slots. + * + * @error conversion-slot-filter-to-permissive + */ + throw new CKEditorError( 'conversion-slot-filter-to-permissive', this, { element, slotsMap } ); } if ( uniqueChildrenInSlots.size != element.childCount ) { - throw new Error( 'not all children covered by slots' ); + /** + * A filter provided to `conversionApi.slotFor()` is to restrictive and leads to missing some nodes while downcasting. + * + * @error conversion-slot-filter-to-restrictive + */ + throw new CKEditorError( 'conversion-slot-filter-to-restrictive', this, { element, slotsMap } ); } } @@ -2066,3 +2087,26 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { * attribute element. If the descriptor is applied to an element, usually these attributes will be set on that element, however, * this depends on how the element converts the descriptor. */ + +/** + * TODO + * + * @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction + * @param {module:engine/model/element~Element} element TODO + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi TODO with mixed slotFor + * @returns {module:engine/view/element~Element} TODO + * + * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * @see module:engine/conversion/downcasthelpers~insertStructure + */ + +/** + * TODO + * + * @callback module:engine/conversion/downcasthelpers~ConsumerFunction + * @param {module:engine/model/element~Element} element TODO + * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable TODO + * @returns {Boolean} TODO + * + * @see module:engine/conversion/downcasthelpers~insertStructure + */ From 614e1af01f7420ae7020e4cb251f3b71907b52f8 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 23 Aug 2021 17:56:39 +0200 Subject: [PATCH 014/140] Adding JSDocs. --- .../src/conversion/downcasthelpers.js | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index ab50578db3b..865e495c8a1 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2088,12 +2088,65 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { * this depends on how the element converts the descriptor. */ +/** + * A filtering function used to choose model child nodes to be downcasted into a specific view + * {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor "slot"} while converting + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}. + * + * @callback module:engine/conversion/downcasthelpers~SlotFilter + * + * @param {module:engine/model/node~Node} node A model node. + * @returns {Boolean} Whether provided model node should be downcasted into this slot. + * + * @see module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor + * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * @see module:engine/conversion/downcasthelpers~insertStructure + */ + +/** + * An extended {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi `DowncastConversionApi`} that adds a + * {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor `slotFor()`} helper to create placeholders for + * child elements converion. + * + * @interface module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi + * @extends module:engine/conversion/downcastdispatcher~DowncastConversionApi + * + * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * @see module:engine/conversion/downcasthelpers~insertStructure + */ + +/** + * A helper that creates placeholders for child elements. + * + * const viewSlot = conversionApi.slotFor( 'children' ); + * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); + * + * conversionApi.writer.insert( viewPosition, viewSlot ); + * + * It could be filtered to a specific subset of children: + * + * const viewSlot = conversionApi.slotFor( node => node.is( 'element', 'foo' ) ); + * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); + * + * conversionApi.writer.insert( viewPosition, viewSlot ); + * + * While providing a filtered slot make sure to provide slots for all child nodes. A single node can not be downcasted into + * multiple slots. + * + * **Note**: You should not change an order of nodes, view elements should be in the same order as model nodes. + * + * @method #slotFor + * @param {'children'|module:engine/conversion/downcasthelpers~SlotFilter} modeOrFilter The filter for child nodes. + * @returns {module:engine/view/element~Element} The slot element to be placed in to the view structure while processing + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}. + */ + /** * TODO * * @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction * @param {module:engine/model/element~Element} element TODO - * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi TODO with mixed slotFor + * @param {module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi} conversionApi TODO with mixed slotFor * @returns {module:engine/view/element~Element} TODO * * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure From 5a95106835884a6d7606af3d75904af6a0fcf7a7 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 31 Aug 2021 15:14:54 +0200 Subject: [PATCH 015/140] Added docs. --- .../src/conversion/conversion.js | 1 + .../src/conversion/downcasthelpers.js | 109 ++++++++++++++++-- 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/conversion.js b/packages/ckeditor5-engine/src/conversion/conversion.js index a75acb6556f..d812576d2fb 100644 --- a/packages/ckeditor5-engine/src/conversion/conversion.js +++ b/packages/ckeditor5-engine/src/conversion/conversion.js @@ -140,6 +140,7 @@ export default class Conversion { * * downcast (model-to-view) conversion helpers: * * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`}, + * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}, * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement `attributeToElement()`}, * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute `attributeToAttribute()`}. * * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToElement `markerToElement()`}. diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 865e495c8a1..840feb5a247 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -98,7 +98,87 @@ export default class DowncastHelpers extends ConversionHelpers { } /** - * TODO + * Model element to view structure conversion helper. + * + * This conversion results in creating a view structure with slots for child nodes. For example, model `` becomes + * `
${ slot for table rows }` in the view. + * + * Simple slot for all child nodes: + * + * editor.conversion.for( 'downcast' ).elementToStructure( { + * model: 'wrappedParagraph', + * view: ( modelElement, conversionApi ) => { + * const { writer, slotFor } = conversionApi; + * + * const wrapperViewElement = writer.createContainerElement( 'div', { class: 'wrapper' } ); + * const paragraphViewElement = writer.createContainerElement( 'p' ); + * + * writer.insert( writer.createPositionAt( wrapperViewElement, 0 ), paragraphViewElement ); + * writer.insert( writer.createPositionAt( paragraphViewElement, 0 ), slotFor( 'children' ) ); + * + * return wrapperViewElement; + * } + * } ); + * + * The conversion with slots dedicated for specific model children and with reconversion trigger defined. + * + * editor.conversion.for( 'downcast' ).elementToStructure( { + * model: { + * name: 'table', + * attributes: [ 'headingRows' ], + * children: true + * }, + * view: ( modelElement, conversionApi ) => { + * const { writer, slotFor } = conversionApi; + * + * const figureElement = writer.createContainerElement( 'figure', { class: 'table' } ); + * const tableElement = writer.createContainerElement( 'table' ); + * + * writer.insert( writer.createPositionAt( figureElement, 0 ), tableElement ); + * + * const headingRows = modelElement.getAttribute( 'headingRows' ) || 0; + * + * if ( headingRows > 0 ) { + * const tableHead = writer.createContainerElement( 'thead' ); + * + * const headSlot = slotFor( element => element.is( 'element', 'tableRow' ) && element.index < headingRows ); + * + * writer.insert( writer.createPositionAt( tableElement, 'end' ), tableHead ); + * writer.insert( writer.createPositionAt( tableHead, 0 ), headSlot ); + * } + * + * if ( headingRows < tableUtils.getRows( table ) ) { + * const tableBody = writer.createContainerElement( 'tbody' ); + * + * const bodySlot = slotFor( element => element.is( 'element', 'tableRow' ) && element.index >= headingRows ); + * + * writer.insert( writer.createPositionAt( tableElement, 'end' ), tableBody ); + * writer.insert( writer.createPositionAt( tableBody, 0 ), bodySlot ); + * } + * + * const restSlot = slotFor( element => !element.is( 'element', 'tableRow' ) ); + * + * writer.insert( writer.createPositionAt( figureElement, 'end' ), restSlot ); + * + * return figureElement; + * } + * } ); + * + * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter + * to the conversion process. + * + * @method #elementToStructure + * @param {Object} config Conversion configuration. + * @param {String|Object} config.model The description or a name of the model element to convert. + * @param {String} [config.model.name] The name of the model element to convert. + * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating + * the view structure. Note that the view will be reconverted if any of the listed attributes will change. + * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list + * of model child nodes changed. + * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function + * that takes the model element and {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi downcast + * conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into. + * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers} */ elementToStructure( config ) { return this.add( downcastElementToStructure( config ) ); @@ -844,6 +924,8 @@ export function insertElement( elementCreator ) { * It is expected that the passed element creator function returns an {@link module:engine/view/element~Element} with attached slots * created with `conversionApi.slotFor()` to indicate where child nodes should be converted. * + * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * * @protected * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} elementCreator Function returning a view structure, * which will be inserted. @@ -1470,15 +1552,15 @@ function downcastElementToElement( config ) { // // @param {Object} config Conversion configuration. // @param {String|Object} config.model +// @param {String} [config.model.name] // @param {Array.} [config.model.attributes] // @param {Boolean} [config.model.children] -// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view +// @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view // @returns {Function} Conversion helper. function downcastElementToStructure( config ) { config = cloneDeep( config ); config.model = normalizeModelElementConfig( config.model ); - config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { dispatcher.on( @@ -1894,7 +1976,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod // @param {String} model.name The name of element. // @param {Array.} model.attributes The list of attribute names that should trigger reconversion. // @param {Boolean} [model.children] Whether the child list change should trigger reconversion. -// @returns {Function} +// @returns {module:engine/conversion/downcasthelpers~ConsumerFunction} function createConsumer( model ) { return ( node, consumable ) => { const events = [ 'insert' ]; @@ -2142,24 +2224,27 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { */ /** - * TODO + * A function that takes the model element and {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi downcast + * conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into. * * @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction - * @param {module:engine/model/element~Element} element TODO - * @param {module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi} conversionApi TODO with mixed slotFor - * @returns {module:engine/view/element~Element} TODO + * @param {module:engine/model/element~Element} element The model element to be converted to the view structure. + * @param {module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi} conversionApi The conversion interface with + * {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor `slotFor()`} factory. + * @returns {module:engine/view/element~Element} The view structure with slots for model child nodes. * * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure * @see module:engine/conversion/downcasthelpers~insertStructure */ /** - * TODO + * A function that is expected to consume all the consumables that were used by the element creator. * * @callback module:engine/conversion/downcasthelpers~ConsumerFunction - * @param {module:engine/model/element~Element} element TODO - * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable TODO - * @returns {Boolean} TODO + * @param {module:engine/model/element~Element} element The model element to be converted to the view structure. + * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The `ModelConsumable` same as in + * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#consumable `DowncastConversionApi.consumable`}. + * @returns {Boolean} `true` if all consumable values were available and were consumed, `false` otherwise. * * @see module:engine/conversion/downcasthelpers~insertStructure */ From 29ac32b0661ee79f9c3de28446b5be5c9eb44fde Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 31 Aug 2021 16:59:06 +0200 Subject: [PATCH 016/140] API cleaning. --- .../src/controller/datacontroller.js | 12 ++++++------ .../src/conversion/downcastdispatcher.js | 8 ++++---- .../src/conversion/downcasthelpers.js | 2 +- packages/ckeditor5-engine/src/dev-utils/model.js | 6 +++--- .../tests/conversion/downcastdispatcher.js | 4 ++-- .../tests/conversion/downcasthelpers.js | 14 +++++++------- packages/ckeditor5-heading/src/title.js | 6 +++--- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js index 885097845ce..cf17c3302c6 100644 --- a/packages/ckeditor5-engine/src/controller/datacontroller.js +++ b/packages/ckeditor5-engine/src/controller/datacontroller.js @@ -247,11 +247,11 @@ export default class DataController { // For model element, we need to check which markers are intersecting with this element and relatively modify the markers' ranges. // Collapsed markers at element boundary, although considered as not intersecting with the element, will also be returned. const markers = modelElementOrFragment.is( 'documentFragment' ) ? - Array.from( modelElementOrFragment.markers ) : + modelElementOrFragment.markers : _getMarkersRelativeToElement( modelElementOrFragment ); // We have no view controller and rendering to DOM in DataController so view.change() block is not used here. - this.downcastDispatcher.convertInsert( modelRange, markers, viewWriter, options ); + this.downcastDispatcher.convert( modelRange, markers, viewWriter, options ); return viewDocumentFragment; } @@ -523,11 +523,11 @@ mix( DataController, ObservableMixin ); // at element boundary, it is considered as contained inside the element and marker range is returned. Otherwise, if the marker is // intersecting with the element, the intersection is returned. function _getMarkersRelativeToElement( element ) { - const result = []; + const result = new Map(); const doc = element.root.document; if ( !doc ) { - return []; + return result; } const elementRange = ModelRange._createIn( element ); @@ -539,12 +539,12 @@ function _getMarkersRelativeToElement( element ) { const isMarkerAtElementBoundary = markerRange.start.isEqual( elementRange.start ) || markerRange.end.isEqual( elementRange.end ); if ( isMarkerCollapsed && isMarkerAtElementBoundary ) { - result.push( [ marker.name, markerRange ] ); + result.set( marker.name, markerRange ); } else { const updatedMarkerRange = elementRange.getIntersection( markerRange ); if ( updatedMarkerRange ) { - result.push( [ marker.name, updatedMarkerRange ] ); + result.set( marker.name, updatedMarkerRange ); } } } diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 17b6ca4fd0f..c1a1c2c1cd4 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -175,17 +175,17 @@ export default class DowncastDispatcher { } /** - * Starts a conversion of a range and provided markers. + * Starts a conversion of a model range and the provided markers. * * @fires insert * @fires attribute * @fires addMarker * @param {module:engine/model/range~Range} range The inserted range. - * @param {Array.} markers The list of marker entries `[ name, range ]` that should be down-casted. + * @param {Map} markers The map of markers that should be down-casted. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. * @param {Object} [options] Optional options object passed to `convertionApi.options`. */ - convertInsert( range, markers, writer, options = {} ) { + convert( range, markers, writer, options = {} ) { const conversionApi = this._prepareConversionApi( writer, options ); this._convertInsert( range, conversionApi ); @@ -535,7 +535,7 @@ export default class DowncastDispatcher { consumable: new Consumable(), writer, options, - convertInsert: range => this._convertInsert( range, conversionApi ) + convert: range => this._convertInsert( range, conversionApi ) }; return conversionApi; diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 840feb5a247..fdb6e4aa4ab 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2130,7 +2130,7 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { } } else { // Note that this is not creating another consumable, it's using the current one. - conversionApi.convertInsert( ModelRange._createOn( modelChildNode ) ); + conversionApi.convert( ModelRange._createOn( modelChildNode ) ); } } } diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js index f9b18a04abc..91f076c190c 100644 --- a/packages/ckeditor5-engine/src/dev-utils/model.js +++ b/packages/ckeditor5-engine/src/dev-utils/model.js @@ -260,18 +260,18 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu return writer.createUIElement( name ); } ) ); - const markerEntries = []; + const markersMap = new Map(); if ( markers ) { // To provide stable results, sort markers by name. for ( const marker of Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ) ) { - markerEntries.push( [ marker.name, marker.getRange() ] ); + markersMap.set( marker.name, marker.getRange() ); } } // Convert model to view. const writer = view._writer; - downcastDispatcher.convertInsert( range, markerEntries, writer ); + downcastDispatcher.convert( range, markersMap, writer ); // Convert model selection to view selection. if ( selection ) { diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 2bfb8a93231..f0e4085c3ce 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -238,7 +238,7 @@ describe( 'DowncastDispatcher', () => { expect( conversionApi.consumable.consume( data.item, 'attribute:' + key ) ).to.be.true; } ); - dispatcher.convertInsert( range, [] ); + dispatcher.convert( range, [] ); // Check the data passed to called events and the order of them. expect( loggedEvents ).to.deep.equal( [ @@ -272,7 +272,7 @@ describe( 'DowncastDispatcher', () => { const range = model.createRangeIn( root ); - dispatcher.convertInsert( range, [] ); + dispatcher.convert( range, [] ); expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 4646c633b8b..91bc56457ac 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -3788,7 +3788,7 @@ describe( 'downcast selection converters', () => { view.change( writer => { const markers = [ [ marker.name, marker.getRange() ] ]; - dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3815,7 +3815,7 @@ describe( 'downcast selection converters', () => { view.change( writer => { const markers = [ [ marker.name, marker.getRange() ] ]; - dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3845,7 +3845,7 @@ describe( 'downcast selection converters', () => { view.change( writer => { const markers = [ [ marker.name, marker.getRange() ] ]; - dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3875,7 +3875,7 @@ describe( 'downcast selection converters', () => { view.change( writer => { const markers = [ [ marker.name, marker.getRange() ] ]; - dispatcher.convertInsert( model.createRangeIn( modelRoot ), markers, writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); @@ -3920,7 +3920,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), [], writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), [], writer ); // Add ui element to view. const uiElement = new ViewUIElement( viewDocument, 'span' ); @@ -3945,7 +3945,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), [], writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), [], writer ); // Add ui element to view. const uiElement = new ViewUIElement( viewDocument, 'span' ); @@ -4226,7 +4226,7 @@ describe( 'downcast selection converters', () => { // Convert model to view. view.change( writer => { - dispatcher.convertInsert( model.createRangeIn( modelRoot ), model.markers, writer ); + dispatcher.convert( model.createRangeIn( modelRoot ), model.markers, writer ); dispatcher.convertSelection( docSelection, model.markers, writer ); } ); diff --git a/packages/ckeditor5-heading/src/title.js b/packages/ckeditor5-heading/src/title.js index d9e6cdf6a73..e93b926689e 100644 --- a/packages/ckeditor5-heading/src/title.js +++ b/packages/ckeditor5-heading/src/title.js @@ -157,20 +157,20 @@ export default class Title extends Plugin { const bodyStartPosition = model.createPositionAfter( root.getChild( 0 ) ); const bodyRange = model.createRange( bodyStartPosition, model.createPositionAt( root, 'end' ) ); - const markers = []; + const markers = new Map(); for ( const marker of model.markers ) { const intersection = bodyRange.getIntersection( marker.getRange() ); if ( intersection ) { - markers.push( [ marker.name, intersection ] ); + markers.set( marker.name, intersection ); } } // Convert the entire root to view. data.mapper.clearBindings(); data.mapper.bindElements( root, viewDocumentFragment ); - data.downcastDispatcher.convertInsert( rootRange, markers, viewWriter, options ); + data.downcastDispatcher.convert( rootRange, markers, viewWriter, options ); // Remove title element from view. viewWriter.remove( viewWriter.createRangeOn( viewDocumentFragment.getChild( 0 ) ) ); From ba694cbf84ca6c0356fba5be84dbd3ec38a7f26c Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 31 Aug 2021 17:50:14 +0200 Subject: [PATCH 017/140] Removed some PoC changes. --- .../src/conversion/downcasthelpers.js | 43 +- .../tests/manual/slotconversion.html | 12 - .../src/htmlembedediting.js | 2 +- .../src/converters/downcast.js | 408 +++++++++++++++--- .../table-cell-refresh-post-fixer.js | 75 ++++ .../table-heading-rows-refresh-post-fixer.js | 55 +++ packages/ckeditor5-table/src/tableediting.js | 94 ++-- .../table-cell-refresh-post-fixer.js | 4 +- 8 files changed, 525 insertions(+), 168 deletions(-) create mode 100644 packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js create mode 100644 packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index fdb6e4aa4ab..1e877971901 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -903,8 +903,6 @@ export function insertElement( elementCreator ) { return; } - // TODO throw if viewElement has any children - this should be handled by toStructure converters - if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { return; } @@ -913,8 +911,6 @@ export function insertElement( elementCreator ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - - reinsertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } @@ -1516,33 +1512,10 @@ function removeHighlight( highlightDescriptor ) { function downcastElementToElement( config ) { config = cloneDeep( config ); - config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { - dispatcher.on( 'insert:' + config.model.name, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); - - if ( config.model.children || config.model.attributes.length ) { - dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); - } - - if ( config.triggerBy ) { - dispatcher.on( 'reduceChanges', createChangeReducer( config.model, ( node, change ) => { - if ( !config.triggerBy( node, change ) ) { - return null; - } - - const elements = []; - - for ( const { item } of ModelRange._createOn( node ) ) { - if ( item.is( 'element', config.model.name ) ) { - elements.push( item ); - } - } - - return { elements, keepChange: true }; - } ) ); - } + dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); }; } @@ -1896,7 +1869,7 @@ function createChangeReducerCallback( model ) { } } - return { elements: [ node ] }; + return [ node ]; }; } @@ -1911,7 +1884,6 @@ function createChangeReducerCallback( model ) { function createChangeReducer( model, callback = createChangeReducerCallback( model ) ) { return ( evt, data ) => { const reducedChanges = []; - const additionalChanges = []; if ( !data.reconvertedElements ) { data.reconvertedElements = new Set(); @@ -1922,7 +1894,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod // For insert or remove use parent element because we need to check if it's added/removed child. const node = change.position ? change.position.parent : change.range.start.nodeAfter; - const { elements, keepChange } = callback( node, change ) || {}; + const elements = callback( node, change ); if ( !elements ) { reducedChanges.push( change ); @@ -1930,15 +1902,12 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod continue; } - if ( keepChange ) { - reducedChanges.push( change ); - } - for ( const element of elements ) { // Do not reconvert just inserted elements. // This is needed for the reconversion triggered by some other change (for example a paragraph inside a table cell). const positionBefore = ModelPosition._createBefore( element ); + // Do not reconvert an element that was just inserted in the same changes list. if ( data.changes.find( change => change.type == 'insert' && change.position.isEqual( positionBefore ) ) ) { continue; } @@ -1952,7 +1921,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod const position = ModelPosition._createBefore( element ); - ( keepChange ? additionalChanges : reducedChanges ).push( { + reducedChanges.push( { type: 'remove', name: element.name, position, @@ -1966,7 +1935,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod } } - data.changes = [ ...reducedChanges, ...additionalChanges ]; + data.changes = reducedChanges; }; } diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index 64881ec086e..aa666f43305 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -60,18 +60,6 @@

elementToStructure

Separate converters for box (with slots) and boxField

- -
- - - - - - - - -
ab
cd
-
diff --git a/packages/ckeditor5-html-embed/src/htmlembedediting.js b/packages/ckeditor5-html-embed/src/htmlembedediting.js index 49eddfeed74..675a15c91a4 100644 --- a/packages/ckeditor5-html-embed/src/htmlembedediting.js +++ b/packages/ckeditor5-html-embed/src/htmlembedediting.js @@ -118,7 +118,7 @@ export default class HtmlEmbedEditing extends Plugin { } } ); - editor.conversion.for( 'editingDowncast' ).elementToElement( { + editor.conversion.for( 'editingDowncast' ).elementToStructure( { model: { name: 'rawHtml', attributes: [ 'value' ] }, view: ( modelElement, { writer } ) => { let domContentWrapper, state, props; diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index c4b529fcaee..ea93c40f4d8 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -12,10 +12,26 @@ import { setHighlightHandling, toWidget, toWidgetEditable } from 'ckeditor5/src/ import { toArray } from 'ckeditor5/src/utils'; /** - * TODO + * Model table element to view table element conversion helper. + * + * This conversion helper creates the whole table element with child elements. + * + * @param {Object} options + * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @returns {Function} Conversion helper. */ -export function downcastTable( tableUtils, options = {} ) { - return ( table, conversionApi ) => { +export function downcastInsertTable( options = {} ) { + return dispatcher => dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => { + const table = data.item; + + if ( !conversionApi.consumable.consume( table, 'insert' ) ) { + return; + } + + // Consume attributes if present to not fire attribute change downcast + conversionApi.consumable.consume( table, 'attribute:headingRows:table' ); + conversionApi.consumable.consume( table, 'attribute:headingColumns:table' ); + const asWidget = options && options.asWidget; const figureElement = conversionApi.writer.createContainerElement( 'figure', { class: 'table' } ); @@ -28,49 +44,111 @@ export function downcastTable( tableUtils, options = {} ) { tableWidget = toTableWidget( figureElement, conversionApi.writer ); } + const tableWalker = new TableWalker( table ); + const tableAttributes = { headingRows: table.getAttribute( 'headingRows' ) || 0, headingColumns: table.getAttribute( 'headingColumns' ) || 0 }; - // Table head slot. - if ( tableAttributes.headingRows > 0 ) { - const tableHead = conversionApi.writer.createContainerElement( 'thead' ); + // Cache for created table rows. + const viewRows = new Map(); + + for ( const tableSlot of tableWalker ) { + const { row, cell } = tableSlot; - const headSlot = conversionApi.slotFor( - element => element.is( 'element', 'tableRow' ) && element.index < tableAttributes.headingRows - ); + const tableRow = table.getChild( row ); + const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); + viewRows.set( row, trElement ); - conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableElement, 'end' ), tableHead ); - conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableHead, 0 ), headSlot ); + // Consume table cell - it will be always consumed as we convert whole table at once. + conversionApi.consumable.consume( cell, 'insert' ); + + const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); + + createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ); } - // Table body slot. - if ( tableAttributes.headingRows < tableUtils.getRows( table ) ) { - const tableBody = conversionApi.writer.createContainerElement( 'tbody' ); + // Insert empty TR elements if there are any rows without anchored cells. Since the model is always normalized + // this can happen only in the document fragment that only part of the table is down-casted. + for ( const tableRow of table.getChildren() ) { + const rowIndex = tableRow.index; + + // Make sure that this is a table row and not some other element (i.e., caption). + if ( tableRow.is( 'element', 'tableRow' ) && !viewRows.has( rowIndex ) ) { + viewRows.set( rowIndex, createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) ); + } + } - const bodySlot = conversionApi.slotFor( - element => element.is( 'element', 'tableRow' ) && element.index >= tableAttributes.headingRows - ); + const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); - conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableElement, 'end' ), tableBody ); - conversionApi.writer.insert( conversionApi.writer.createPositionAt( tableBody, 0 ), bodySlot ); + conversionApi.mapper.bindElements( table, asWidget ? tableWidget : figureElement ); + conversionApi.writer.insert( viewPosition, asWidget ? tableWidget : figureElement ); + } ); +} + +/** + * Model row element to view `` element conversion helper. + * + * This conversion helper creates the whole `` element with child elements. + * + * @returns {Function} Conversion helper. + */ +export function downcastInsertRow() { + return dispatcher => dispatcher.on( 'insert:tableRow', ( evt, data, conversionApi ) => { + const tableRow = data.item; + + if ( !conversionApi.consumable.consume( tableRow, 'insert' ) ) { + return; } - // Slot for the rest (for example caption). - const restSlot = conversionApi.slotFor( element => !element.is( 'element', 'tableRow' ) ); + const table = tableRow.parent; + + const figureElement = conversionApi.mapper.toViewElement( table ); + const tableElement = getViewTable( figureElement ); + + const row = table.getChildIndex( tableRow ); + + const tableWalker = new TableWalker( table, { row } ); + + const tableAttributes = { + headingRows: table.getAttribute( 'headingRows' ) || 0, + headingColumns: table.getAttribute( 'headingColumns' ) || 0 + }; - conversionApi.writer.insert( conversionApi.writer.createPositionAt( figureElement, 'end' ), restSlot ); + // Cache for created table rows. + const viewRows = new Map(); - return asWidget ? tableWidget : figureElement; - }; + for ( const tableSlot of tableWalker ) { + const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); + viewRows.set( row, trElement ); + + // Consume table cell - it will be always consumed as we convert whole row at once. + conversionApi.consumable.consume( tableSlot.cell, 'insert' ); + + const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); + + createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); + } + } ); } /** - * TODO + * Model table cell element to view `` or `` element conversion helper. + * + * This conversion helper will create proper `` elements for table cells that are in the heading section (heading row or column) + * and `` otherwise. + * + * @returns {Function} Conversion helper. */ -export function downcastCell( options = {} ) { - return ( tableCell, conversionApi ) => { +export function downcastInsertCell() { + return dispatcher => dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => { + const tableCell = data.item; + + if ( !conversionApi.consumable.consume( tableCell, 'insert' ) ) { + return; + } + const tableRow = tableCell.parent; const table = tableRow.parent; const rowIndex = table.getChildIndex( tableRow ); @@ -85,10 +163,78 @@ export function downcastCell( options = {} ) { // We need to iterate over a table in order to get proper row & column values from a walker for ( const tableSlot of tableWalker ) { if ( tableSlot.cell === tableCell ) { - return createViewTableCellElement( tableSlot, tableAttributes, conversionApi, options ); + const trElement = conversionApi.mapper.toViewElement( tableRow ); + const insertPosition = conversionApi.writer.createPositionAt( trElement, tableRow.getChildIndex( tableCell ) ); + + createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); + + // No need to iterate further. + return; } } - }; + } ); +} + +/** + * Conversion helper that acts on heading column table attribute change. + * + * Depending on changed attributes this converter will rename `` elements or vice versa depending on the cell column index. + * + * @returns {Function} Conversion helper. + */ +export function downcastTableHeadingColumnsChange() { + return dispatcher => dispatcher.on( 'attribute:headingColumns:table', ( evt, data, conversionApi ) => { + const table = data.item; + + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const tableAttributes = { + headingRows: table.getAttribute( 'headingRows' ) || 0, + headingColumns: table.getAttribute( 'headingColumns' ) || 0 + }; + + const oldColumns = data.attributeOldValue; + const newColumns = data.attributeNewValue; + + const lastColumnToCheck = ( oldColumns > newColumns ? oldColumns : newColumns ) - 1; + + for ( const tableSlot of new TableWalker( table, { endColumn: lastColumnToCheck } ) ) { + renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ); + } + } ); +} + +/** + * Conversion helper that acts on a removed row. + * + * @returns {Function} Conversion helper. + */ +export function downcastRemoveRow() { + return dispatcher => dispatcher.on( 'remove:tableRow', ( evt, data, conversionApi ) => { + // Prevent default remove converter. + evt.stop(); + const viewWriter = conversionApi.writer; + const mapper = conversionApi.mapper; + + const viewStart = mapper.toViewPosition( data.position ).getLastMatchingPosition( value => !value.item.is( 'element', 'tr' ) ); + const viewItem = viewStart.nodeAfter; + const tableSection = viewItem.parent; + const viewTable = tableSection.parent; + + // Remove associated from the view. + const removeRange = viewWriter.createRangeOn( viewItem ); + const removed = viewWriter.remove( removeRange ); + + for ( const child of viewWriter.createRangeIn( removed ).getItems() ) { + mapper.unbindViewElement( child ); + } + + // Cleanup: Ensure that thead & tbody sections are removed if left empty after removing rows. See #6437, #6391. + removeTableSectionIfEmpty( 'thead', viewTable, conversionApi ); + removeTableSectionIfEmpty( 'tbody', viewTable, conversionApi ); + }, { priority: 'higher' } ); } /** @@ -104,27 +250,18 @@ export function downcastCell( options = {} ) { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi * @returns {module:engine/view/containerelement~ContainerElement|undefined} */ -export function convertParagraphInTableCell( options = {} ) { - return ( modelElement, conversionApi ) => { - const asWidget = options && options.asWidget; - const { writer } = conversionApi; - - if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { - return; - } +export function convertParagraphInTableCell( modelElement, conversionApi ) { + const { writer } = conversionApi; - if ( isSingleParagraphWithoutAttributes( modelElement ) ) { - if ( asWidget ) { - return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); - } else { - // Additional requirement for data pipeline to have backward compatible data tables. - conversionApi.consumable.consume( modelElement, 'insert' ); - conversionApi.mapper.bindElements( modelElement, conversionApi.mapper.toViewElement( modelElement.parent ) ); + if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { + return; + } - return null; - } - } - }; + if ( isSingleParagraphWithoutAttributes( modelElement ) ) { + return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); + } else { + return writer.createContainerElement( 'p' ); + } } /** @@ -160,12 +297,61 @@ function toTableWidget( viewElement, writer ) { return toWidget( viewElement, writer, { hasSelectionHandle: true } ); } +// Renames an existing table cell in the view to a given element name. +// +// **Note** This method will not do anything if a view table cell has not been converted yet. +// +// @param {module:engine/model/element~Element} tableCell +// @param {String} desiredCellElementName +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +function renameViewTableCell( tableCell, desiredCellElementName, conversionApi ) { + const viewWriter = conversionApi.writer; + const viewCell = conversionApi.mapper.toViewElement( tableCell ); + + const editable = viewWriter.createEditableElement( desiredCellElementName, viewCell.getAttributes() ); + const renamedCell = toWidgetEditable( editable, viewWriter ); + + setHighlightHandling( + renamedCell, + viewWriter, + ( element, descriptor, writer ) => writer.addClass( toArray( descriptor.classes ), element ), + ( element, descriptor, writer ) => writer.removeClass( toArray( descriptor.classes ), element ) + ); + + viewWriter.insert( viewWriter.createPositionAfter( viewCell ), renamedCell ); + viewWriter.move( viewWriter.createRangeIn( viewCell ), viewWriter.createPositionAt( renamedCell, 0 ) ); + viewWriter.remove( viewWriter.createRangeOn( viewCell ) ); + + conversionApi.mapper.unbindViewElement( viewCell ); + conversionApi.mapper.bindElements( tableCell, renamedCell ); +} + +// Renames a table cell element in the view according to its location in the table. +// +// @param {module:table/tablewalker~TableSlot} tableSlot +// @param {{headingColumns, headingRows}} tableAttributes +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +function renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ) { + const { cell } = tableSlot; + + // Check whether current columnIndex is overlapped by table cells from previous rows. + const desiredCellElementName = getCellElementName( tableSlot, tableAttributes ); + + const viewCell = conversionApi.mapper.toViewElement( cell ); + + // If in single change we're converting attribute changes and inserting cell the table cell might not be inserted into view + // because of child conversion is done after parent. + if ( viewCell && viewCell.name !== desiredCellElementName ) { + renameViewTableCell( cell, desiredCellElementName, conversionApi ); + } +} + // Creates a table cell element in the view. // // @param {module:table/tablewalker~TableSlot} tableSlot -// TODO +// @param {module:engine/view/position~Position} insertPosition // @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function createViewTableCellElement( tableSlot, tableAttributes, conversionApi, options ) { +function createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ) { const asWidget = options && options.asWidget; const cellElementName = getCellElementName( tableSlot, tableAttributes ); @@ -182,7 +368,52 @@ function createViewTableCellElement( tableSlot, tableAttributes, conversionApi, ); } - return cellElement; + const tableCell = tableSlot.cell; + + const firstChild = tableCell.getChild( 0 ); + const isSingleParagraph = tableCell.childCount === 1 && firstChild.name === 'paragraph'; + + conversionApi.writer.insert( insertPosition, cellElement ); + + conversionApi.mapper.bindElements( tableCell, cellElement ); + + // Additional requirement for data pipeline to have backward compatible data tables. + if ( !asWidget && isSingleParagraph && !hasAnyAttribute( firstChild ) ) { + const innerParagraph = tableCell.getChild( 0 ); + + conversionApi.consumable.consume( innerParagraph, 'insert' ); + + conversionApi.mapper.bindElements( innerParagraph, cellElement ); + } +} + +// Creates a `` view element. +// +// @param {module:engine/view/element~Element} tableElement +// @param {module:engine/model/element~Element} tableRow +// @param {Number} rowIndex +// @param {{headingColumns, headingRows}} tableAttributes +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @returns {module:engine/view/element~Element} +function createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) { + // Will always consume since we're converting element from a parent . + conversionApi.consumable.consume( tableRow, 'insert' ); + + const trElement = tableRow.isEmpty ? + conversionApi.writer.createEmptyElement( 'tr' ) : + conversionApi.writer.createContainerElement( 'tr' ); + + conversionApi.mapper.bindElements( tableRow, trElement ); + + const headingRows = tableAttributes.headingRows; + const tableSection = getOrCreateTableSection( getSectionName( rowIndex, tableAttributes ), tableElement, conversionApi ); + + const offset = headingRows > 0 && rowIndex >= headingRows ? rowIndex - headingRows : rowIndex; + const position = conversionApi.writer.createPositionAt( tableSection, offset ); + + conversionApi.writer.insert( position, trElement ); + + return trElement; } // Returns `th` for heading cells and `td` for other cells for the current table walker value. @@ -208,6 +439,81 @@ function getCellElementName( tableSlot, tableAttributes ) { return isRowHeading ? 'th' : 'td'; } +// Returns the table section name for the current table walker value. +// +// @param {Number} row +// @param {{headingColumns, headingRows}} tableAttributes +// @returns {String} +function getSectionName( row, tableAttributes ) { + return row < tableAttributes.headingRows ? 'thead' : 'tbody'; +} + +// Creates or returns an existing `` or `` element with caching. +// +// @param {String} sectionName +// @param {module:engine/view/element~Element} viewTable +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @param {Object} cachedTableSection An object that stores cached elements. +// @returns {module:engine/view/containerelement~ContainerElement} +function getOrCreateTableSection( sectionName, viewTable, conversionApi ) { + const viewTableSection = getExistingTableSectionElement( sectionName, viewTable ); + + return viewTableSection ? viewTableSection : createTableSection( sectionName, viewTable, conversionApi ); +} + +// Finds an existing `` or `` element or returns undefined. +// +// @param {String} sectionName +// @param {module:engine/view/element~Element} tableElement +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +function getExistingTableSectionElement( sectionName, tableElement ) { + for ( const tableSection of tableElement.getChildren() ) { + if ( tableSection.name == sectionName ) { + return tableSection; + } + } +} + +// Creates a table section at the end of the table. +// +// @param {String} sectionName +// @param {module:engine/view/element~Element} tableElement +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @returns {module:engine/view/containerelement~ContainerElement} +function createTableSection( sectionName, tableElement, conversionApi ) { + const tableChildElement = conversionApi.writer.createContainerElement( sectionName ); + + const insertPosition = conversionApi.writer.createPositionAt( tableElement, sectionName == 'tbody' ? 'end' : 0 ); + + conversionApi.writer.insert( insertPosition, tableChildElement ); + + return tableChildElement; +} + +// Removes an existing `` or `` element if it is empty. +// +// @param {String} sectionName +// @param {module:engine/view/element~Element} tableElement +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +function removeTableSectionIfEmpty( sectionName, tableElement, conversionApi ) { + const tableSection = getExistingTableSectionElement( sectionName, tableElement ); + + if ( tableSection && tableSection.childCount === 0 ) { + conversionApi.writer.remove( conversionApi.writer.createRangeOn( tableSection ) ); + } +} + +// Finds a '
' element inside the `
` widget. +// +// @param {module:engine/view/element~Element} viewFigure +function getViewTable( viewFigure ) { + for ( const child of viewFigure.getChildren() ) { + if ( child.name === 'table' ) { + return child; + } + } +} + // Checks if an element has any attributes set. // // @param {module:engine/model/element~Element element diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js new file mode 100644 index 00000000000..c132e3a329a --- /dev/null +++ b/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js @@ -0,0 +1,75 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module table/converters/table-cell-refresh-post-fixer + */ + +import { isSingleParagraphWithoutAttributes } from './downcast'; + +/** + * Injects a table cell post-fixer into the model which marks the table cell in the differ to have it re-rendered. + * + * Model `paragraph` inside a table cell can be rendered as `` or `

`. It is rendered as `` if this is the only block + * element in that table cell and it does not have any attributes. It is rendered as `

` otherwise. + * + * When table cell content changes, for example a second `paragraph` element is added, we need to ensure that the first `paragraph` is + * re-rendered so it changes from `` to `

`. The easiest way to do it is to re-render the entire table cell. + * + * @param {module:engine/model/model~Model} model + * @param {module:engine/conversion/mapper~Mapper} mapper + */ +export default function injectTableCellRefreshPostFixer( model, mapper ) { + model.document.registerPostFixer( () => tableCellRefreshPostFixer( model.document.differ, mapper ) ); +} + +function tableCellRefreshPostFixer( differ, mapper ) { + // Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes. + + // 1. Gather all changes inside table cell. + const cellsToCheck = new Set(); + + for ( const change of differ.getChanges() ) { + const parent = change.type == 'attribute' ? change.range.start.parent : change.position.parent; + + if ( parent.is( 'element', 'tableCell' ) ) { + cellsToCheck.add( parent ); + } + } + + // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: Checking table cell to refresh (${ cellsToCheck.size }).` ); + // @if CK_DEBUG_TABLE // let paragraphsRefreshed = 0; + + for ( const tableCell of cellsToCheck.values() ) { + for ( const paragraph of [ ...tableCell.getChildren() ].filter( child => shouldRefresh( child, mapper ) ) ) { + // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing paragraph in table cell (${++paragraphsRefreshed}).` ); + differ.refreshItem( paragraph ); + } + } + + // Always return false to prevent the refresh post-fixer from re-running on the same set of changes and going into an infinite loop. + // This "post-fixer" does not change the model structure so there shouldn't be need to run other post-fixers again. + // See https://github.com/ckeditor/ckeditor5/issues/1936 & https://github.com/ckeditor/ckeditor5/issues/8200. + return false; +} + +// Check if given model element needs refreshing. +// +// @param {module:engine/model/element~Element} modelElement +// @param {module:engine/conversion/mapper~Mapper} mapper +// @returns {Boolean} +function shouldRefresh( child, mapper ) { + if ( !child.is( 'element', 'paragraph' ) ) { + return false; + } + + const viewElement = mapper.toViewElement( child ); + + if ( !viewElement ) { + return false; + } + + return isSingleParagraphWithoutAttributes( child ) !== viewElement.is( 'element', 'span' ); +} diff --git a/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js new file mode 100644 index 00000000000..b3810fd6cfb --- /dev/null +++ b/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js @@ -0,0 +1,55 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module table/converters/table-heading-rows-refresh-post-fixer + */ + +/** + * Injects a table post-fixer into the model which marks the table in the differ to have it re-rendered. + * + * Table heading rows are represented in the model by a `headingRows` attribute. However, in the view, it's represented as separate + * sections of the table (`

` or ``) and changing `headingRows` attribute requires moving table rows between two sections. + * This causes problems with structural changes in a table (like adding and removing rows) thus atomic converters cannot be used. + * + * When table `headingRows` attribute changes, the entire table is re-rendered. + * + * @param {module:engine/model/model~Model} model + */ +export default function injectTableHeadingRowsRefreshPostFixer( model ) { + model.document.registerPostFixer( () => tableHeadingRowsRefreshPostFixer( model ) ); +} + +function tableHeadingRowsRefreshPostFixer( model ) { + const differ = model.document.differ; + + // Stores tables to be refreshed so the table will be refreshed once for multiple changes. + const tablesToRefresh = new Set(); + + for ( const change of differ.getChanges() ) { + if ( change.type != 'attribute' ) { + continue; + } + + const element = change.range.start.nodeAfter; + + if ( element && element.is( 'element', 'table' ) && change.attributeKey == 'headingRows' ) { + tablesToRefresh.add( element ); + } + } + + if ( tablesToRefresh.size ) { + // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing heading rows (${ tablesToRefresh.size }).` ); + + for ( const table of tablesToRefresh.values() ) { + // Should be handled by a `triggerBy` configuration. See: https://github.com/ckeditor/ckeditor5/issues/8138. + differ.refreshItem( table ); + } + + return true; + } + + return false; +} diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index 79843ba9b37..838d1643f16 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -9,9 +9,15 @@ import { Plugin } from 'ckeditor5/src/core'; -import TableUtils from '../src/tableutils'; import upcastTable, { ensureParagraphInTableCell, skipEmptyTableRow, upcastTableFigure } from './converters/upcasttable'; -import { convertParagraphInTableCell, downcastCell, downcastTable } from './converters/downcast'; +import { + convertParagraphInTableCell, + downcastInsertCell, + downcastInsertRow, + downcastInsertTable, + downcastRemoveRow, + downcastTableHeadingColumnsChange +} from './converters/downcast'; import InsertTableCommand from './commands/inserttablecommand'; import InsertRowCommand from './commands/insertrowcommand'; @@ -25,9 +31,12 @@ import SetHeaderColumnCommand from './commands/setheadercolumncommand'; import MergeCellsCommand from './commands/mergecellscommand'; import SelectRowCommand from './commands/selectrowcommand'; import SelectColumnCommand from './commands/selectcolumncommand'; +import TableUtils from '../src/tableutils'; import injectTableLayoutPostFixer from './converters/table-layout-post-fixer'; import injectTableCellParagraphPostFixer from './converters/table-cell-paragraph-post-fixer'; +import injectTableCellRefreshPostFixer from './converters/table-cell-refresh-post-fixer'; +import injectTableHeadingRowsRefreshPostFixer from './converters/table-heading-rows-refresh-post-fixer'; import '../theme/tableediting.css'; @@ -44,13 +53,6 @@ export default class TableEditing extends Plugin { return 'TableEditing'; } - /** - * @inheritDoc - */ - static get requires() { - return [ TableUtils ]; - } - /** * @inheritDoc */ @@ -59,7 +61,6 @@ export default class TableEditing extends Plugin { const model = editor.model; const schema = model.schema; const conversion = editor.conversion; - const tableUtils = editor.plugins.get( TableUtils ); schema.register( 'table', { allowWhere: '$block', @@ -87,33 +88,15 @@ export default class TableEditing extends Plugin { // Table conversion. conversion.for( 'upcast' ).add( upcastTable() ); - conversion.for( 'editingDowncast' ).elementToStructure( { - model: { - name: 'table', - attributes: [ 'headingRows' ], - children: true - }, - view: downcastTable( tableUtils, { asWidget: true } ) - } ); - conversion.for( 'dataDowncast' ).elementToStructure( { - model: { - name: 'table', - attributes: [ 'headingRows' ], - children: true - }, - view: downcastTable( tableUtils ) - } ); + conversion.for( 'editingDowncast' ).add( downcastInsertTable( { asWidget: true } ) ); + conversion.for( 'dataDowncast' ).add( downcastInsertTable() ); // Table row conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); conversion.for( 'upcast' ).add( skipEmptyTableRow() ); - conversion.for( 'downcast' ).elementToElement( { - model: 'tableRow', - view: ( modelElement, { writer } ) => ( - modelElement.isEmpty ? writer.createEmptyElement( 'tr' ) : writer.createContainerElement( 'tr' ) - ) - } ); + conversion.for( 'editingDowncast' ).add( downcastInsertRow() ); + conversion.for( 'editingDowncast' ).add( downcastRemoveRow() ); // Table cell conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'td' } ); @@ -121,43 +104,12 @@ export default class TableEditing extends Plugin { conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'td' ) ); conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'th' ) ); - conversion.for( 'editingDowncast' ).elementToElement( { - model: 'tableCell', - view: downcastCell( { asWidget: true } ), - triggerBy: ( node, change ) => ( - node.is( 'element', 'table' ) && change.type == 'attribute' && - [ 'headingRows', 'headingColumns' ].includes( change.attributeKey ) - ) - } ); - conversion.for( 'dataDowncast' ).elementToElement( { - model: 'tableCell', - view: downcastCell() - } ); + conversion.for( 'editingDowncast' ).add( downcastInsertCell() ); // Duplicates code - needed to properly refresh paragraph inside a table cell. conversion.for( 'editingDowncast' ).elementToElement( { model: 'paragraph', - view: convertParagraphInTableCell( { asWidget: true } ), - triggerBy: ( node, change ) => { - if ( node.is( 'element', 'tableCell' ) ) { - const isReducedToSingleParagraph = node.childCount == 1 && change.type == 'remove'; - const isExtendedFromSingleParagraph = node.childCount > 1 && change.type == 'insert' && change.position.offset <= 1; - - return isReducedToSingleParagraph || isExtendedFromSingleParagraph; - } else if ( change.type == 'attribute' && node.is( 'element', 'paragraph' ) && node.parent.is( 'element', 'tableCell' ) ) { - const isSingleChildElement = node.parent.childCount == 1; - const isAttributeSetOrClear = change.attributeOldValue == null || change.attributeNewValue == null; - - // TODO maybe we should be able to access the old attributes (from differ) - // to check if there were no attributes and now there is any - return isSingleChildElement && isAttributeSetOrClear; - } - }, - converterPriority: 'high' - } ); - conversion.for( 'dataDowncast' ).elementToElement( { - model: 'paragraph', - view: convertParagraphInTableCell(), + view: convertParagraphInTableCell, converterPriority: 'high' } ); @@ -174,6 +126,9 @@ export default class TableEditing extends Plugin { view: 'rowspan' } ); + // Table heading columns conversion (a change of heading rows requires a reconversion of the whole table). + conversion.for( 'editingDowncast' ).add( downcastTableHeadingColumnsChange() ); + // Manually adjust model position mappings in a special case, when a table cell contains a paragraph, which is bound // to its parent (to the table cell). This custom model-to-view position mapping is necessary in data pipeline only, // because only during this conversion a paragraph can be bound to its parent. @@ -209,9 +164,18 @@ export default class TableEditing extends Plugin { editor.commands.add( 'selectTableRow', new SelectRowCommand( editor ) ); editor.commands.add( 'selectTableColumn', new SelectColumnCommand( editor ) ); + injectTableHeadingRowsRefreshPostFixer( model ); injectTableLayoutPostFixer( model ); + injectTableCellRefreshPostFixer( model, editor.editing.mapper ); injectTableCellParagraphPostFixer( model ); } + + /** + * @inheritDoc + */ + static get requires() { + return [ TableUtils ]; + } } // Creates a mapper callback to adjust model position mappings in a table cell containing a paragraph, which is bound to its parent diff --git a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js index b66a8302730..9481853d89f 100644 --- a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js +++ b/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js @@ -297,7 +297,7 @@ describe( 'Table cell refresh post-fixer', () => { assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ [ '

00

' ] ], { asWidget: true } ) ); - expect( getViewForParagraph( table ) ).to.not.equal( previousView ); + expect( getViewForParagraph( table ) ).to.equal( previousView ); } ); it( 'should keep

in the view when adding another attribute to a and removing attribute that is already set', () => { @@ -314,7 +314,7 @@ describe( 'Table cell refresh post-fixer', () => { assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ [ '

00

' ] ], { asWidget: true } ) ); - expect( getViewForParagraph( table ) ).to.not.equal( previousView ); + expect( getViewForParagraph( table ) ).to.equal( previousView ); } ); it( 'should keep

in the view when attribute value is changed (table cell with multiple blocks)', () => { From b0d0b2d02816fe4e2809171bee6f1e3c9e9bba5d Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 31 Aug 2021 20:11:08 +0200 Subject: [PATCH 018/140] Code cleaning. Added tests for DowncastDispatcher. --- .../src/conversion/downcastdispatcher.js | 6 +- .../src/conversion/downcasthelpers.js | 9 - .../tests/conversion/downcastdispatcher.js | 529 +++++++++++++++--- .../tests/converters/downcast.js | 2 +- 4 files changed, 457 insertions(+), 89 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index c1a1c2c1cd4..c0c95a6799f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -120,10 +120,10 @@ export default class DowncastDispatcher { /** * A template for an interface passed by the dispatcher to the event callbacks. * - * @private + * @protected * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi} */ - this.conversionApi = { dispatcher: this, ...conversionApi }; + this._conversionApi = { dispatcher: this, ...conversionApi }; } /** @@ -531,7 +531,7 @@ export default class DowncastDispatcher { */ _prepareConversionApi( writer, options = {} ) { const conversionApi = { - ...this.conversionApi, + ...this._conversionApi, consumable: new Consumable(), writer, options, diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 1e877971901..e0b96ef1e5f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1903,15 +1903,6 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod } for ( const element of elements ) { - // Do not reconvert just inserted elements. - // This is needed for the reconversion triggered by some other change (for example a paragraph inside a table cell). - const positionBefore = ModelPosition._createBefore( element ); - - // Do not reconvert an element that was just inserted in the same changes list. - if ( data.changes.find( change => change.type == 'insert' && change.position.isEqual( positionBefore ) ) ) { - continue; - } - // If it's already marked for reconversion, so skip this change. if ( data.reconvertedElements.has( element ) ) { continue; diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index f0e4085c3ce..af525144060 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -11,20 +11,23 @@ import ModelText from '../../src/model/text'; import ModelElement from '../../src/model/element'; import ModelDocumentFragment from '../../src/model/documentfragment'; import ModelRange from '../../src/model/range'; +import ModelConsumable from '../../src/conversion/modelconsumable'; import View from '../../src/view/view'; import ViewContainerElement from '../../src/view/containerelement'; +import DowncastWriter from '../../src/view/downcastwriter'; import { StylesProcessor } from '../../src/view/stylesmap'; describe( 'DowncastDispatcher', () => { - let dispatcher, doc, root, differStub, model, view, mapper; + let dispatcher, doc, root, differStub, model, view, mapper, apiObj; beforeEach( () => { model = new Model(); view = new View( new StylesProcessor() ); doc = model.document; mapper = new Mapper(); - dispatcher = new DowncastDispatcher( { mapper } ); + apiObj = {}; + dispatcher = new DowncastDispatcher( { mapper, apiObj } ); root = doc.createRoot(); differStub = { @@ -35,17 +38,18 @@ describe( 'DowncastDispatcher', () => { } ); describe( 'constructor()', () => { - it( 'should create DowncastDispatcher with given api', () => { + it( 'should create DowncastDispatcher with given api template', () => { const apiObj = {}; const dispatcher = new DowncastDispatcher( { apiObj } ); - expect( dispatcher.conversionApi.apiObj ).to.equal( apiObj ); + expect( dispatcher._conversionApi.apiObj ).to.equal( apiObj ); } ); } ); describe( 'convertChanges', () => { it( 'should call _convertInsert for insert change', () => { sinon.stub( dispatcher, '_convertInsert' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); @@ -59,12 +63,39 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertInsert.calledOnce ).to.be.true; expect( dispatcher._convertInsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertInsert.firstCall.args[ 1 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should call _convertReinsert for reinsert change', () => { + sinon.stub( dispatcher, '_convertReinsert' ); + sinon.stub( mapper, 'flushDeferredBindings' ); + + const position = model.createPositionFromPath( root, [ 0 ] ); + const range = ModelRange._createFromPositionAndShift( position, 1 ); + + differStub.getChanges = () => [ { type: 'reinsert', position, length: 1 } ]; + + view.change( writer => { + dispatcher.convertChanges( differStub, model.markers, writer ); + } ); + + expect( dispatcher._convertReinsert.calledOnce ).to.be.true; + expect( dispatcher._convertReinsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true; + + assertConversionApi( dispatcher._convertReinsert.firstCall.args[ 1 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should call _convertRemove for remove change', () => { sinon.stub( dispatcher, '_convertRemove' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const position = model.createPositionFromPath( root, [ 0 ] ); @@ -76,12 +107,16 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertRemove.calledWith( position, 2, '$text' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertRemove.firstCall.args[ 3 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); - it.skip( 'should call _convertAttribute for attribute change', () => { + it( 'should call _convertAttribute for attribute change', () => { sinon.stub( dispatcher, '_convertAttribute' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); @@ -96,20 +131,26 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertAttribute.calledWith( range, 'key', null, 'foo' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertAttribute.firstCall.args[ 4 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); - it.skip( 'should handle multiple changes', () => { + it( 'should handle multiple changes', () => { sinon.stub( dispatcher, '_convertInsert' ); + sinon.stub( dispatcher, '_convertReinsert' ); sinon.stub( dispatcher, '_convertRemove' ); sinon.stub( dispatcher, '_convertAttribute' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const position = model.createPositionFromPath( root, [ 0 ] ); const range = ModelRange._createFromPositionAndShift( position, 1 ); differStub.getChanges = () => [ { type: 'insert', position, length: 1 }, + { type: 'reinsert', position, length: 1 }, { type: 'attribute', position, range, attributeKey: 'key', attributeOldValue: null, attributeNewValue: 'foo' }, { type: 'remove', position, length: 1, name: 'paragraph' }, { type: 'insert', position, length: 3 } @@ -120,15 +161,55 @@ describe( 'DowncastDispatcher', () => { } ); expect( dispatcher._convertInsert.calledTwice ).to.be.true; + expect( dispatcher._convertReinsert.calledOnce ).to.be.true; expect( dispatcher._convertRemove.calledOnce ).to.be.true; expect( dispatcher._convertAttribute.calledOnce ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should fire "reduceChanges" event and use replaced changes', () => { + sinon.stub( dispatcher, '_convertInsert' ); + sinon.stub( dispatcher, '_convertReinsert' ); + sinon.stub( dispatcher, '_convertRemove' ); + sinon.stub( dispatcher, '_convertAttribute' ); + sinon.stub( mapper, 'flushDeferredBindings' ); + + const position = model.createPositionFromPath( root, [ 0 ] ); + const range = ModelRange._createFromPositionAndShift( position, 1 ); + + differStub.getChanges = () => [ + { type: 'insert', position, length: 1 }, + { type: 'attribute', position, range, attributeKey: 'key', attributeOldValue: null, attributeNewValue: 'foo' } + ]; + + dispatcher.on( 'reduceChanges', ( evt, data ) => { + data.changes = [ + { type: 'insert', position, length: 1 }, + { type: 'remove', position, length: 1 }, + { type: 'reinsert', position, length: 1 } + ]; + } ); + + view.change( writer => { + dispatcher.convertChanges( differStub, model.markers, writer ); + } ); + + expect( dispatcher._convertInsert.calledOnce ).to.be.true; + expect( dispatcher._convertReinsert.calledOnce ).to.be.true; + expect( dispatcher._convertRemove.calledOnce ).to.be.true; + expect( dispatcher._convertAttribute.notCalled ).to.be.true; + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should call _convertMarkerAdd when markers are added', () => { sinon.stub( dispatcher, '_convertMarkerAdd' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -145,12 +226,17 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertMarkerAdd.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.secondCall.args[ 2 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should call _convertMarkerRemove when markers are removed', () => { sinon.stub( dispatcher, '_convertMarkerRemove' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -167,13 +253,18 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertMarkerRemove.calledWith( 'foo', fooRange ) ); expect( dispatcher._convertMarkerRemove.calledWith( 'bar', barRange ) ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertMarkerRemove.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerRemove.secondCall.args[ 2 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should re-render markers which view elements got unbound during conversion', () => { sinon.stub( dispatcher, '_convertMarkerRemove' ); sinon.stub( dispatcher, '_convertMarkerAdd' ); + sinon.stub( mapper, 'flushDeferredBindings' ); const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); @@ -182,7 +273,7 @@ describe( 'DowncastDispatcher', () => { model.markers._set( 'bar', barRange ); // Stub `Mapper#flushUnboundMarkerNames`. - dispatcher.conversionApi.mapper.flushUnboundMarkerNames = () => [ 'foo', 'bar' ]; + dispatcher._conversionApi.mapper.flushUnboundMarkerNames = () => [ 'foo', 'bar' ]; view.change( writer => { dispatcher.convertChanges( differStub, model.markers, writer ); @@ -193,12 +284,18 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + assertConversionApi( dispatcher._convertMarkerRemove.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerRemove.secondCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.secondCall.args[ 2 ] ); + + expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); - describe( 'convertInsert', () => { + describe( 'convert', () => { it( 'should fire event with correct parameters for every item in passed range', () => { root._appendChild( [ new ModelText( 'foo', { bold: true } ), @@ -252,8 +349,8 @@ describe( 'DowncastDispatcher', () => { 'attribute:italic:true:$text:xx:7,0:7,2' ] ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire events for already consumed parts of model', () => { @@ -282,12 +379,285 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should call _convertMarkerAdd for all provided markers', () => { + sinon.stub( dispatcher, '_convertMarkerAdd' ); + + const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); + const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); + + const markers = new Map( [ + [ 'foo', fooRange ], + [ 'bar', barRange ] + ] ); + + const range = model.createRangeIn( root ); + + view.change( writer => { + dispatcher.convert( range, markers, writer ); + } ); + + expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); + + assertConversionApi( dispatcher._convertMarkerAdd.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.secondCall.args[ 2 ] ); + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should pass options object to conversionApi', () => { + sinon.stub( dispatcher, '_convertInsert' ); + sinon.stub( dispatcher, '_convertMarkerAdd' ); + + const position = model.createPositionFromPath( root, [ 0 ] ); + const range = ModelRange._createFromPositionAndShift( position, 1 ); + + const fooRange = model.createRange( model.createPositionAt( root, 0 ), model.createPositionAt( root, 1 ) ); + const barRange = model.createRange( model.createPositionAt( root, 3 ), model.createPositionAt( root, 6 ) ); + + const markers = new Map( [ + [ 'foo', fooRange ], + [ 'bar', barRange ] + ] ); + + const options = {}; + + view.change( writer => { + dispatcher.convert( range, markers, writer, options ); + } ); + + expect( dispatcher._convertInsert.calledOnce ).to.be.true; + expect( dispatcher._convertInsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true; + + expect( dispatcher._convertMarkerAdd.calledWith( 'foo', fooRange ) ); + expect( dispatcher._convertMarkerAdd.calledWith( 'bar', barRange ) ); + + assertConversionApi( dispatcher._convertInsert.firstCall.args[ 1 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.firstCall.args[ 2 ] ); + assertConversionApi( dispatcher._convertMarkerAdd.secondCall.args[ 2 ] ); + + expect( dispatcher._convertInsert.firstCall.args[ 1 ].options ).to.equal( options ); + expect( dispatcher._convertMarkerAdd.firstCall.args[ 2 ].options ).to.equal( options ); + expect( dispatcher._convertMarkerAdd.firstCall.args[ 2 ].options ).to.equal( options ); + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); - describe( 'convertRemove', () => { + describe( '_convertInsert', () => { + it( 'should fire event with correct parameters for every item in passed range', () => { + root._appendChild( [ + new ModelText( 'foo', { bold: true } ), + new ModelElement( 'imageBlock', null, new ModelElement( 'caption' ) ), + new ModelText( 'bar' ), + new ModelElement( 'paragraph', { class: 'nice' }, new ModelText( 'xx', { italic: true } ) ) + ] ); + + const range = model.createRangeIn( root ); + const loggedEvents = []; + + // We will check everything connected with insert event: + dispatcher.on( 'insert', ( evt, data, conversionApi ) => { + // Check if the item is correct. + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + // Check if the range is correct. + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + // Check if the event name is correct. + expect( evt.name ).to.equal( 'insert:' + ( data.item.name || '$text' ) ); + // Check if model consumable is correct. + expect( conversionApi.consumable.consume( data.item, 'insert' ) ).to.be.true; + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + // Same here. + dispatcher.on( 'attribute', ( evt, data, conversionApi ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + expect( evt.name ).to.equal( 'attribute:' + key + ':' + ( data.item.name || '$text' ) ); + expect( conversionApi.consumable.consume( data.item, 'attribute:' + key ) ).to.be.true; + } ); + + view.change( writer => { + dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + } ); + + // Check the data passed to called events and the order of them. + expect( loggedEvents ).to.deep.equal( [ + 'insert:$text:foo:0:3', + 'attribute:bold:true:$text:foo:0:3', + 'insert:imageBlock:3:4', + 'insert:caption:3,0:3,1', + 'insert:$text:bar:4:7', + 'insert:paragraph:7:8', + 'attribute:class:nice:paragraph:7:8', + 'insert:$text:xx:7,0:7,2', + 'attribute:italic:true:$text:xx:7,0:7,2' + ] ); + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should not fire events for already consumed parts of model', () => { + root._appendChild( [ + new ModelElement( 'imageBlock', { src: 'foo.jpg', title: 'bar', bold: true }, [ + new ModelElement( 'caption', {}, new ModelText( 'title' ) ) + ] ) + ] ); + + sinon.spy( dispatcher, 'fire' ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item.getChild( 0 ), 'insert' ); + conversionApi.consumable.consume( data.item, 'attribute:bold' ); + } ); + + const range = model.createRangeIn( root ); + + view.change( writer => { + dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + } ); + + expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; + expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; + expect( dispatcher.fire.calledWith( 'attribute:title:imageBlock' ) ).to.be.true; + expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.true; + + expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; + expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + + it( 'should not add consumable item if it was added already in consumable', () => { + root._appendChild( [ + new ModelElement( 'imageBlock', {}, [ + new ModelElement( 'caption', {}, new ModelText( 'title' ) ) + ] ) + ] ); + + const loggedEvents = []; + + // We will check everything connected with insert event: + dispatcher.on( 'insert', ( evt, data, conversionApi ) => { + // Check if the item is correct. + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + // Check if the range is correct. + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + // Check if the event name is correct. + expect( evt.name ).to.equal( 'insert:' + ( data.item.name || '$text' ) ); + // Check if model consumable is correct. + expect( conversionApi.consumable.test( data.item, 'insert' ) ).to.be.true; + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + if ( conversionApi.consumable.consume( data.item, 'insert' ) ) { + conversionApi.convert( data.range ); + } + } ); + + dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'insert' ); + } ); + + dispatcher.on( 'insert:$text', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'insert' ); + } ); + + const range = model.createRangeIn( root ); + + view.change( writer => { + dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + } ); + + expect( loggedEvents ).to.deep.equal( [ + 'insert:imageBlock:0:1', + 'insert:caption:0,0:0,1', + 'insert:$text:title:0,0,0:0,0,5' + ] ); + } ); + } ); + + describe( '_convertReinsert', () => { + it( 'should fire event with correct parameters for every item in passed range (shallow)', () => { + root._appendChild( [ + new ModelText( 'foo', { bold: true } ), + new ModelElement( 'imageBlock', null, new ModelElement( 'caption' ) ), + new ModelText( 'bar' ), + new ModelElement( 'paragraph', { class: 'nice' }, new ModelText( 'xx', { italic: true } ) ) + ] ); + + const range = model.createRangeIn( root ); + const loggedEvents = []; + + // We will check everything connected with insert event: + dispatcher.on( 'insert', ( evt, data, conversionApi ) => { + // Check if the item is correct. + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + // Check if the range is correct. + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + // Check if the event name is correct. + expect( evt.name ).to.equal( 'insert:' + ( data.item.name || '$text' ) ); + // Check if model consumable is correct. + expect( conversionApi.consumable.consume( data.item, 'insert' ) ).to.be.true; + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + // Same here. + dispatcher.on( 'attribute', ( evt, data, conversionApi ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + expect( evt.name ).to.equal( 'attribute:' + key + ':' + ( data.item.name || '$text' ) ); + expect( conversionApi.consumable.consume( data.item, 'attribute:' + key ) ).to.be.true; + } ); + + view.change( writer => { + dispatcher._convertReinsert( range, dispatcher._prepareConversionApi( writer ) ); + } ); + + // Check the data passed to called events and the order of them. + expect( loggedEvents ).to.deep.equal( [ + 'insert:$text:foo:0:3', + 'attribute:bold:true:$text:foo:0:3', + 'insert:imageBlock:3:4', + 'insert:$text:bar:4:7', + 'insert:paragraph:7:8', + 'attribute:class:nice:paragraph:7:8' + ] ); + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + } ); + + describe( '_convertRemove', () => { it( 'should fire event for removed range', () => { const loggedEvents = []; @@ -300,8 +670,8 @@ describe( 'DowncastDispatcher', () => { expect( loggedEvents ).to.deep.equal( [ 'remove:3:3' ] ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); @@ -328,8 +698,8 @@ describe( 'DowncastDispatcher', () => { { selection: sinon.match.instanceOf( doc.selection.constructor ) } ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should prepare correct list of consumable values', () => { @@ -348,8 +718,8 @@ describe( 'DowncastDispatcher', () => { dispatcher.convertSelection( doc.selection, model.markers, [] ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire attributes events for non-collapsed selection', () => { @@ -367,8 +737,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'attribute:bold' ) ).to.be.false; expect( dispatcher.fire.calledWith( 'attribute:italic' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should fire attributes events for collapsed selection', () => { @@ -388,8 +758,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'attribute:bold:$text' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire attributes events if attribute has been consumed', () => { @@ -416,8 +786,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'attribute:bold' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should fire events for markers for collapsed selection', () => { @@ -436,8 +806,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'addMarker:name' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should fire events for all markers of the same group for collapsed selection', () => { @@ -465,8 +835,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'addMarker:name:1' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'addMarker:name:2' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire events for markers for non-collapsed selection', () => { @@ -482,8 +852,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'addMarker:name' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire event for marker if selection is in a element with custom highlight handling', () => { @@ -504,7 +874,7 @@ describe( 'DowncastDispatcher', () => { viewFigure._setCustomProperty( 'removeHighlight', () => {} ); // Create mapper mock. - dispatcher.conversionApi.mapper = { + dispatcher._conversionApi.mapper = { toViewElement( modelElement ) { if ( modelElement == image ) { return viewFigure; @@ -527,8 +897,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'addMarker:name' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire events if information about marker has been consumed', () => { @@ -554,12 +924,12 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'addMarker:foo' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'addMarker:bar' ) ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); - describe( 'convertMarkerAdd', () => { + describe( '_convertMarkerAdd', () => { let element, text; beforeEach( () => { @@ -584,8 +954,8 @@ describe( 'DowncastDispatcher', () => { expect( spy.calledOnce ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should convert marker in document fragment', () => { @@ -598,8 +968,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.called ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not convert marker if it is in graveyard', () => { @@ -610,8 +980,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.called ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should fire addMarker event for whole non-collapsed marker and for each item in the range', () => { @@ -651,8 +1021,8 @@ describe( 'DowncastDispatcher', () => { expect( items[ 0 ] ).to.equal( element ); expect( items[ 1 ].data ).to.equal( text.data ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not fire conversion for non-collapsed marker items if marker was consumed in earlier event', () => { @@ -676,8 +1046,8 @@ describe( 'DowncastDispatcher', () => { expect( spyItems.called ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should be possible to override #1', () => { @@ -705,8 +1075,8 @@ describe( 'DowncastDispatcher', () => { expect( addMarkerSpy.called ).to.be.false; expect( highAddMarkerSpy.calledOnce ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should be possible to override #2', () => { @@ -736,12 +1106,12 @@ describe( 'DowncastDispatcher', () => { // Called once for each item, twice total. expect( highAddMarkerSpy.calledTwice ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); - describe( 'convertMarkerRemove', () => { + describe( '_convertMarkerRemove', () => { let range, element, text; beforeEach( () => { @@ -759,8 +1129,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'removeMarker:name' ) ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should not convert marker if it is in graveyard', () => { @@ -771,8 +1141,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.called ).to.be.false; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should convert marker in document fragment', () => { @@ -785,8 +1155,8 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.called ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should fire conversion for the range', () => { @@ -799,8 +1169,8 @@ describe( 'DowncastDispatcher', () => { dispatcher._convertMarkerRemove( 'name', range, dispatcher._prepareConversionApi() ); - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); it( 'should be possible to override', () => { @@ -822,8 +1192,15 @@ describe( 'DowncastDispatcher', () => { expect( removeMarkerSpy.called ).to.be.false; expect( highRemoveMarkerSpy.calledOnce ).to.be.true; - expect( dispatcher.conversionApi.writer ).to.be.undefined; - expect( dispatcher.conversionApi.consumable ).to.be.undefined; + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); } ); + + function assertConversionApi( conversionApi ) { + expect( conversionApi ).to.have.property( 'writer' ).that.is.instanceof( DowncastWriter ); + expect( conversionApi ).to.have.property( 'consumable' ).that.is.instanceof( ModelConsumable ); + expect( conversionApi ).to.have.property( 'mapper' ).that.is.equal( mapper ); + expect( conversionApi ).to.have.property( 'apiObj' ).that.is.equal( apiObj ); + } } ); diff --git a/packages/ckeditor5-table/tests/converters/downcast.js b/packages/ckeditor5-table/tests/converters/downcast.js index 245eb493d16..9818c3e5d1a 100644 --- a/packages/ckeditor5-table/tests/converters/downcast.js +++ b/packages/ckeditor5-table/tests/converters/downcast.js @@ -213,7 +213,7 @@ describe( 'downcast converters', () => { assertEqualMarkup( editor.getData(), '

' + - '' + + '' + '
 

 

' ); } ); From 1a7e15506c7d3ba42fa4e25cba9a236055121823 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 31 Aug 2021 21:01:14 +0200 Subject: [PATCH 019/140] Ported tests for legacy "triggerBy" to test for elementToStructure. --- .../src/conversion/downcasthelpers.js | 1 + .../tests/conversion/downcasthelpers.js | 1715 ++++++++++++----- 2 files changed, 1235 insertions(+), 481 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index e0b96ef1e5f..7e52dad816c 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1534,6 +1534,7 @@ function downcastElementToStructure( config ) { config = cloneDeep( config ); config.model = normalizeModelElementConfig( config.model ); + config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { dispatcher.on( diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 91bc56457ac..3d9efdc6cb6 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -785,611 +785,1332 @@ describe( 'DowncastHelpers', () => { } ); } ); } ); + } ); + } ); - // TODO those tests are for elementToStructure - describe( 'with complex view structure (slot conversion)', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] - } ); - downcastHelpers.elementToStructure( { - model: 'complex', - view: ( modelElement, { writer, mapper, slotFor } ) => { - const classForMain = !!modelElement.getAttribute( 'classForMain' ); - const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); - const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); + describe( 'elementToStructure()', () => { + it( 'should be chainable', () => { + expect( downcastHelpers.elementToStructure( { model: 'paragraph', view: 'p' } ) ).to.equal( downcastHelpers ); + } ); - const outer = writer.createContainerElement( 'div', { - class: `complex-slots${ classForMain ? ' with-class' : '' }` - } ); - const inner = writer.createContainerElement( 'div', { - class: `slots${ classForWrap ? ' with-class' : '' }` - } ); + it( 'config.view is a string', () => { + downcastHelpers.elementToStructure( { model: 'paragraph', view: 'p' } ); - if ( attributeToElement ) { - const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); - writer.insert( writer.createPositionAt( outer, 0 ), optional ); - } + model.change( writer => { + writer.insertElement( 'paragraph', modelRoot, 0 ); + } ); - writer.insert( writer.createPositionAt( outer, 'end' ), inner ); - mapper.bindElements( modelElement, inner ); + expectResult( '

' ); + } ); - writer.insert( writer.createPositionAt( inner, 0 ), slotFor( modelElement, 'children' ) ); + it( 'can be overwritten using converterPriority', () => { + downcastHelpers.elementToStructure( { model: 'paragraph', view: 'p' } ); + downcastHelpers.elementToStructure( { model: 'paragraph', view: 'foo', converterPriority: 'high' } ); - // for ( const slot of modelElement.getChildren() ) { - // const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); - // - // writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); - // mapper.bindElements( slot, viewSlot ); - // } + model.change( writer => { + writer.insertElement( 'paragraph', modelRoot, 0 ); + } ); - return outer; - }, - triggerBy: { - attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ], - children: [ 'slot' ] - } - } ); + expectResult( '' ); + } ); - downcastHelpers.elementToElement( { - model: 'slot', - view: { name: 'div', classes: 'slot' } - } ); + it( 'config.view is a view element definition', () => { + downcastHelpers.elementToStructure( { + model: 'fancyParagraph', + view: { + name: 'p', + classes: 'fancy' + } + } ); - model.schema.register( 'slot', { - allowIn: 'complex' - } ); + model.change( writer => { + writer.insertElement( 'fancyParagraph', modelRoot, 0 ); + } ); - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'slot' - } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); + expectResult( '

' ); + } ); + + it( 'config.view is a function', () => { + downcastHelpers.elementToStructure( { + model: 'heading', + view: ( modelElement, { writer } ) => writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ) + } ); + + model.change( writer => { + writer.insertElement( 'heading', { level: 2 }, modelRoot, 0 ); + } ); + + expectResult( '

' ); + } ); + + describe( 'with simple block view structure (without children)', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] } ); - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + } ); - expectResult( '
' ); + it( 'should convert on insert', () => { + model.change( writer => { + writer.insertElement( 'simpleBlock', modelRoot, 0 ); } ); - it( 'should convert on attribute set (main element)', () => { - setModelData( model, '' ); + expectResult( '
' ); + } ); - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + it( 'should convert on attribute set', () => { + setModelData( model, '' ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + expect( viewAfter ).to.not.equal( viewBefore ); + } ); + + it( 'should convert on attribute change', () => { + setModelData( model, '' ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + } ); + + it( 'should convert on attribute remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on one attribute add and other remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should properly re-bind mapper mappings and retain markers', () => { + downcastHelpers.elementToElement( { + model: 'simpleBlock', + view: ( modelElement, { writer } ) => { + const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + + return toWidget( viewElement, writer ); + }, + triggerBy: { + attributes: [ 'toStyle', 'toClass' ] + }, + converterPriority: 'high' + } ); + + const mapper = controller.mapper; + + downcastHelpers.markerToHighlight( { + model: 'myMarker', + view: { classes: 'foo' } + } ); + + setModelData( model, '' ); + + const modelElement = modelRoot.getChild( 0 ); + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.addMarker( 'myMarker', { range: writer.createRangeOn( modelElement ), usingOperation: false } ); + } ); + + expect( mapper.toViewElement( modelElement ) ).to.equal( viewBefore ); + expect( mapper.toModelElement( viewBefore ) ).to.equal( modelElement ); + expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelElement ); + } ); + + const [ viewAfter ] = getNodes(); + + expect( mapper.toViewElement( modelElement ) ).to.equal( viewAfter ); + expect( mapper.toModelElement( viewBefore ) ).to.be.undefined; + expect( mapper.toModelElement( viewAfter ) ).to.equal( modelElement ); + expect( mapper.markerNameToElements( 'myMarker' ).has( viewAfter ) ).to.be.true; + expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.false; + } ); + + it( 'should do nothing if non-triggerBy attribute has changed', () => { + setModelData( model, '' ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + + expect( viewAfter ).to.equal( viewBefore ); + } ); + } ); + + describe( 'with simple block view structure (with children)', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer, slotFor } ) => { + const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + + writer.insert( writer.createPositionAt( viewElement, 0 ), slotFor( 'children' ) ); + + return viewElement; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert on insert', () => { + model.change( writer => { + const simpleBlock = writer.createElement( 'simpleBlock' ); + const paragraph = writer.createElement( 'paragraph' ); + + writer.insert( simpleBlock, modelRoot, 0 ); + writer.insert( paragraph, simpleBlock, 0 ); + writer.insertText( 'foo', paragraph, 0 ); + } ); + + expectResult( '

foo

' ); + } ); + + it( 'should convert on attribute set', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + + it( 'should convert on attribute change', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + + it( 'should convert on attribute remove', () => { + setModelData( model, 'foo' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '

foo

' ); + } ); + + it( 'should convert on one attribute add and other remove', () => { + setModelData( model, 'foo' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '

foo

' ); + } ); + + it( 'should do nothing if non-triggerBy attribute has changed', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + // TODO - is text always re-converted? + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + } ); + + describe( 'with simple block view structure (with children - reconvert on child add)', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root' + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const viewElement = writer.createContainerElement( 'div' ); + + writer.insert( writer.createPositionAt( viewElement, 0 ), slotFor( 'children' ) ); + + return viewElement; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert on insert', () => { + model.change( writer => { + const simpleBlock = writer.createElement( 'simpleBlock' ); + const paragraph = writer.createElement( 'paragraph' ); + + writer.insert( simpleBlock, modelRoot, 0 ); + writer.insert( paragraph, simpleBlock, 0 ); + writer.insertText( 'foo', paragraph, 0 ); + } ); + + expectResult( '

foo

' ); + } ); + + it( 'should convert on adding a child (at the beginning)', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter ] = getNodes(); + + expectResult( '

bar

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + + it( 'should convert on adding a child (in the middle)', () => { + setModelData( model, + '' + + 'foobar' + + '' ); + + const [ viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'baz' ); - expectResult( '
' ); + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); } ); - it( 'should convert on attribute set (other element)', () => { + const [ viewAfter, + paraFooAfter, textFooAfter, /* insertedPara */, /* insertedText */, paraBarAfter, textBarAfter + ] = getNodes(); + + expectResult( '

foo

baz

bar

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); + expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + } ); + + it( 'should convert on adding a child (at the end)', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

bar

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + + it( 'should convert on removing a child', () => { + setModelData( model, + 'foobar' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + } ); + + // https://github.com/ckeditor/ckeditor5/issues/9641 + it( 'should convert on multiple similar child hooks', () => { + model.schema.register( 'simpleBlock2', { + allowIn: '$root', + allowChildren: 'paragraph' + } ); + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock2', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const viewElement = writer.createContainerElement( 'div', { class: 'second' } ); + + writer.insert( writer.createPositionAt( viewElement, 0 ), slotFor( 'children' ) ); + + return viewElement; + } + } ); + + setModelData( model, + 'foo' + + 'bar' + ); + + const [ viewBefore0, paraBefore0, textBefore0 ] = getNodes( 0 ); + const [ viewBefore1, paraBefore1, textBefore1 ] = getNodes( 1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'abc' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter0, paraAfter0, textAfter0 ] = getNodes( 0 ); + const [ viewAfter1, paraAfter1, textAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( '123' ); + + writer.insert( paragraph, modelRoot.getChild( 1 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfterAfter0, paraAfterAfter0, textAfterAfter0 ] = getNodes( 0 ); + const [ viewAfterAfter1, paraAfterAfter1, textAfterAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

123

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + expect( viewAfterAfter0, 'simpleBlock' ).to.equal( viewAfter0 ); + expect( paraAfterAfter0, 'para' ).to.equal( paraAfter0 ); + expect( textAfterAfter0, 'text' ).to.equal( textAfter0 ); + + expect( viewAfterAfter1, 'simpleBlock' ).to.not.equal( viewAfter1 ); + expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); + expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); + } ); + } ); + + describe( 'with complex view structure - no children allowed', () => { + beforeEach( () => { + model.schema.register( 'complex', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer } ) => { + const outer = writer.createContainerElement( 'div', { class: 'complex-outer' } ); + const inner = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + + writer.insert( writer.createPositionAt( outer, 0 ), inner ); + + return outer; + } + } ); + } ); + + it( 'should convert on insert', () => { + model.change( writer => { + writer.insertElement( 'complex', modelRoot, 0 ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute set', () => { + setModelData( model, '' ); + + const [ outerDivBefore, innerDivBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + } ); + + const [ outerDivAfter, innerDivAfter ] = getNodes(); + + expectResult( '
' ); + expect( outerDivAfter, 'outer div' ).to.not.equal( outerDivBefore ); + expect( innerDivAfter, 'inner div' ).to.not.equal( innerDivBefore ); + } ); + + it( 'should convert on attribute change', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on one attribute add and other remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should do nothing if non-triggerBy attribute has changed', () => { + setModelData( model, '' ); + + const [ outerDivBefore, innerDivBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); + + const [ outerDivAfter, innerDivAfter ] = getNodes(); + + expectResult( '
' ); + + expect( outerDivAfter, 'outer div' ).to.equal( outerDivBefore ); + expect( innerDivAfter, 'inner div' ).to.equal( innerDivBefore ); + } ); + } ); + + describe( 'with complex view structure (without slots)', () => { + beforeEach( () => { + model.schema.register( 'complex', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer, slotFor } ) => { + const outer = writer.createContainerElement( 'c-outer' ); + const inner = writer.createContainerElement( 'c-inner', getViewAttributes( modelElement ) ); + + writer.insert( writer.createPositionAt( outer, 0 ), inner ); + writer.insert( writer.createPositionAt( inner, 0 ), slotFor( 'children' ) ); + + return outer; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'complex' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert on insert', () => { + model.change( writer => { + writer.insertElement( 'complex', modelRoot, 0 ); + } ); + + expectResult( '' ); + } ); + + it( 'should convert on attribute set', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '' ); + } ); + + it( 'should convert on attribute remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '' ); + } ); + + it( 'should convert on one attribute add and other remove', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '' ); + } ); + + it( 'should do nothing if non-triggerBy attribute has changed', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '' ); + } ); + + describe( 'memoization', () => { + it( 'should create new element on re-converting element', () => { setModelData( model, '' ); + const [ outerBefore, innerBefore ] = getNodes(); + model.change( writer => { - writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); } ); - expectResult( '
' ); + const [ outerAfter, innerAfter ] = getNodes(); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( innerAfter, 'inner' ).to.not.equal( innerBefore ); } ); - it( 'should convert on attribute set (insert new view element)', () => { - setModelData( model, '' ); + // Skipped, as it would require two-level mapping. See https://github.com/ckeditor/ckeditor5/issues/1589. + // Doable as a similar case works in table scenario for table cells (table is refreshed). + it.skip( 'should not re-create child elements on re-converting element', () => { + setModelData( model, 'Foo bar baz' ); + + expectResult( '

Foo bar baz

' ); + const renderedViewView = viewRoot.getChild( 0 ).getChild( 0 ); model.change( writer => { - writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); } ); - expectResult( '
' ); + const viewAfterReRender = viewRoot.getChild( 0 ).getChild( 0 ); + + expect( viewAfterReRender ).to.equal( renderedViewView ); } ); + } ); + } ); - it( 'should convert element with slots', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + describe( 'with complex view structure (single slot for all child nodes)', () => { + beforeEach( () => { + model.schema.register( 'complex', { + allowIn: '$root', + allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] + } ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + - '
' - ); + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ], + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const classForMain = !!modelElement.getAttribute( 'classForMain' ); + const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); + const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); + + const outer = writer.createContainerElement( 'div', { + class: `complex-slots${ classForMain ? ' with-class' : '' }` + } ); + + const inner = writer.createContainerElement( 'div', { + class: `slots${ classForWrap ? ' with-class' : '' }` + } ); + + if ( attributeToElement ) { + const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); + + writer.insert( writer.createPositionAt( outer, 0 ), optional ); + } + + writer.insert( writer.createPositionAt( outer, 'end' ), inner ); + writer.insert( writer.createPositionAt( inner, 0 ), slotFor( 'children' ) ); + + return outer; + } + } ); + + model.schema.register( 'slot', { + allowIn: 'complex' + } ); + + downcastHelpers.elementToElement( { + model: 'slot', + view: { name: 'div', classes: 'slot' } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'slot' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert on insert', () => { + model.change( writer => { + writer.insertElement( 'complex', modelRoot, 0 ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute set (main element)', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute set (other element)', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute set (insert new view element)', () => { + setModelData( model, '' ); + + model.change( writer => { + writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert element with slots', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); + + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '
' + + '
' + ); + } ); + + it( 'should convert element on adding slot', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); + + model.change( writer => { + insertBazSlot( writer, modelRoot ); + } ); + + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + + '
' + ); + } ); + + it( 'should convert element on removing slot', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); + + model.change( writer => { + writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + } ); + + expectResult( + '
' + + '
' + + '

bar

' + + '
' + + '
' + ); + } ); + + it( 'should convert element on multiple triggers (remove + insert)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); + + model.change( writer => { + writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + insertBazSlot( writer, modelRoot ); } ); - it( 'should convert element on adding slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

bar

' + + '

baz

' + + '
' + + '
' + ); + } ); - model.change( writer => { - insertBazSlot( writer, modelRoot ); - } ); + it( 'should convert element on multiple triggers (remove + attribute)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + model.change( writer => { + writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert element on removing slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

bar

' + + '
' + + '
' + ); + } ); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - } ); + it( 'should convert element on multiple triggers (insert + attribute)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); + model.change( writer => { + insertBazSlot( writer, modelRoot ); + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert element on multiple triggers (remove + insert)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + + '
' + ); + } ); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - insertBazSlot( writer, modelRoot ); - } ); + it( 'should not trigger refresh on adding a slot to an element without triggerBy conversion', () => { + model.schema.register( 'other', { + allowIn: '$root' + } ); + model.schema.extend( 'slot', { + allowIn: 'other' + } ); + downcastHelpers.elementToElement( { + model: 'other', + view: { + name: 'div', + classes: 'other' + } + } ); + downcastHelpers.elementToElement( { + model: 'slot', + view: { + name: 'div', + classes: 'slot' + } + } ); - expectResult( - '
' + - '
' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + setModelData( model, + '' + + 'foo' + + 'bar' + + '' + ); + const otherView = viewRoot.getChild( 0 ); + + model.change( writer => { + insertBazSlot( writer, modelRoot ); } ); - it( 'should convert element on multiple triggers (remove + attribute)', () => { - setModelData( model, - '' + + expectResult( + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + ); + const otherViewAfter = viewRoot.getChild( 0 ); + + expect( otherView, 'the view should not be refreshed' ).to.equal( otherViewAfter ); + } ); + + describe( 'memoization', () => { + it( 'should create new element on re-converting element', () => { + setModelData( model, '' + 'foo' + 'bar' + - '' ); + '
' + ); + + const [ complexView ] = getNodes(); model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); + const [ viewAfterReRender ] = getNodes(); + + expect( viewAfterReRender, 'the view should be refreshed' ).to.not.equal( complexView ); } ); - it( 'should convert element on multiple triggers (insert + attribute)', () => { - setModelData( model, - '' + + it( 'should not re-create slot\'s child elements on re-converting main element (attribute changed)', () => { + setModelData( model, '' + 'foo' + 'bar' + - '' ); + '' + ); + + const [ main, /* unused */, + slotOne, paraOne, textNodeOne, + slotTwo, paraTwo, textNodeTwo ] = getNodes(); model.change( writer => { - insertBazSlot( writer, modelRoot ); writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + const [ mainAfter, /* unused */, + slotOneAfter, paraOneAfter, textNodeOneAfter, + slotTwoAfter, paraTwoAfter, textNodeTwoAfter ] = getNodes(); + + expect( mainAfter, 'main view' ).to.not.equal( main ); + expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); + expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); + expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); + expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); + expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); + expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); } ); - it( 'should not trigger refresh on adding a slot to an element without triggerBy conversion', () => { - model.schema.register( 'other', { - allowIn: '$root' - } ); - model.schema.extend( 'slot', { - allowIn: 'other' - } ); - downcastHelpers.elementToElement( { - model: 'other', - view: { - name: 'div', - classes: 'other' - } - } ); - downcastHelpers.elementToElement( { - model: 'slot', - view: { - name: 'div', - classes: 'slot' - } - } ); - - setModelData( model, - '' + + it( 'should not re-create slot\'s child elements on re-converting main element (slot added)', () => { + setModelData( model, '' + 'foo' + 'bar' + - '' + '' ); - const otherView = viewRoot.getChild( 0 ); + + const [ main, /* unused */, + slotOne, paraOne, textNodeOne, + slotTwo, paraTwo, textNodeTwo ] = getNodes(); model.change( writer => { - insertBazSlot( writer, modelRoot ); + const slot = writer.createElement( 'slot' ); + const paragraph = writer.createElement( 'paragraph' ); + writer.insertText( 'baz', paragraph, 0 ); + writer.insert( paragraph, slot, 0 ); + writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); } ); - expectResult( - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' - ); - const otherViewAfter = viewRoot.getChild( 0 ); + const [ mainAfter, /* unused */, + slotOneAfter, paraOneAfter, textNodeOneAfter, + slotTwoAfter, paraTwoAfter, textNodeTwoAfter, + slotThreeAfter, paraThreeAfter, textNodeThreeAfter + ] = getNodes(); - expect( otherView, 'the view should not be refreshed' ).to.equal( otherViewAfter ); + expect( mainAfter, 'main view' ).to.not.equal( main ); + expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); + expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); + expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); + expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); + expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); + expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); + expect( slotThreeAfter, 'third slot view' ).to.not.be.undefined; + expect( paraThreeAfter, 'third slot paragraph view' ).to.not.be.undefined; + expect( textNodeThreeAfter, 'third slot text node view' ).to.not.be.undefined; } ); + } ); + } ); - describe( 'memoization', () => { - it( 'should create new element on re-converting element', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); - - const [ complexView ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + // Skipped, as it would require two-level mapping. + describe.skip( 'with complex view structure (slot conversion atomic converters for some changes)', () => { + beforeEach( () => { + model.schema.register( 'complex', { + allowIn: '$root', + allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] + } ); - const [ viewAfterReRender ] = getNodes(); + function createViewSlot( slot, { writer, mapper } ) { + const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); - expect( viewAfterReRender, 'the view should be refreshed' ).to.not.equal( complexView ); - } ); + mapper.bindElements( slot, viewSlot ); - it( 'should not re-create slot\'s child elements on re-converting main element (attribute changed)', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); + return viewSlot; + } - const [ main, /* unused */, - slotOne, paraOne, textNodeOne, - slotTwo, paraTwo, textNodeTwo ] = getNodes(); + downcastHelpers.elementToElement( { + model: 'complex', + view: ( modelElement, { writer, mapper, consumable } ) => { + const classForMain = !!modelElement.getAttribute( 'classForMain' ); + const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); + const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + const outer = writer.createContainerElement( 'div', { + class: `complex-slots${ classForMain ? ' with-class' : '' }` } ); - - const [ mainAfter, /* unused */, - slotOneAfter, paraOneAfter, textNodeOneAfter, - slotTwoAfter, paraTwoAfter, textNodeTwoAfter ] = getNodes(); - - expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); - expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); - expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); - expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); - expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); - } ); - - it( 'should not re-create slot\'s child elements on re-converting main element (slot added)', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); - - const [ main, /* unused */, - slotOne, paraOne, textNodeOne, - slotTwo, paraTwo, textNodeTwo ] = getNodes(); - - model.change( writer => { - const slot = writer.createElement( 'slot' ); - const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); + const inner = writer.createContainerElement( 'div', { + class: `slots${ classForWrap ? ' with-class' : '' }` } ); - const [ mainAfter, /* unused */, - slotOneAfter, paraOneAfter, textNodeOneAfter, - slotTwoAfter, paraTwoAfter, textNodeTwoAfter, - slotThreeAfter, paraThreeAfter, textNodeThreeAfter - ] = getNodes(); - - expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); - expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); - expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); - expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); - expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); - expect( slotThreeAfter, 'third slot view' ).to.not.be.undefined; - expect( paraThreeAfter, 'third slot paragraph view' ).to.not.be.undefined; - expect( textNodeThreeAfter, 'third slot text node view' ).to.not.be.undefined; - } ); - } ); - } ); - - // Skipped, as it would require two-level mapping. See https://github.com/ckeditor/ckeditor5/issues/1589. - describe.skip( 'with complex view structure (slot conversion atomic converters for some changes)', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] - } ); - - function createViewSlot( slot, { writer, mapper } ) { - const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); - - mapper.bindElements( slot, viewSlot ); - - return viewSlot; - } - - downcastHelpers.elementToElement( { - model: 'complex', - view: ( modelElement, { writer, mapper, consumable } ) => { - const classForMain = !!modelElement.getAttribute( 'classForMain' ); - const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); - const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); - - const outer = writer.createContainerElement( 'div', { - class: `complex-slots${ classForMain ? ' with-class' : '' }` - } ); - const inner = writer.createContainerElement( 'div', { - class: `slots${ classForWrap ? ' with-class' : '' }` - } ); - - if ( attributeToElement ) { - const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); - writer.insert( writer.createPositionAt( outer, 0 ), optional ); - } - - writer.insert( writer.createPositionAt( outer, 'end' ), inner ); - mapper.bindElements( modelElement, outer ); - mapper.bindElements( modelElement, inner ); + if ( attributeToElement ) { + const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); + writer.insert( writer.createPositionAt( outer, 0 ), optional ); + } - for ( const slot of modelElement.getChildren() ) { - const viewSlot = createViewSlot( slot, { writer, mapper } ); + writer.insert( writer.createPositionAt( outer, 'end' ), inner ); + mapper.bindElements( modelElement, outer ); + mapper.bindElements( modelElement, inner ); - writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); - consumable.consume( slot, 'insert' ); - } + for ( const slot of modelElement.getChildren() ) { + const viewSlot = createViewSlot( slot, { writer, mapper } ); - return outer; - }, - triggerBy: { - attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] - // Contrary to the previous test - do not act on child changes. - // children: [ 'slot' ] + writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); + consumable.consume( slot, 'insert' ); } - } ); - downcastHelpers.elementToElement( { - model: 'slot', - view: createViewSlot - } ); - model.schema.register( 'slot', { - allowIn: 'complex' - } ); + return outer; + }, + triggerBy: { + attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] + // Contrary to the previous test - do not act on child changes. + // children: [ 'slot' ] + } + } ); + downcastHelpers.elementToElement( { + model: 'slot', + view: createViewSlot + } ); - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'slot' - } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); + model.schema.register( 'slot', { + allowIn: 'complex' } ); - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'slot' + } ); + downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); + } ); - expectResult( '
' ); + it( 'should convert on insert', () => { + model.change( writer => { + writer.insertElement( 'complex', modelRoot, 0 ); } ); - it( 'should convert on attribute set (main element)', () => { - setModelData( model, '' ); + expectResult( '
' ); + } ); - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + it( 'should convert on attribute set (main element)', () => { + setModelData( model, '' ); - expectResult( '
' ); + model.change( writer => { + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert on attribute set (other element)', () => { - setModelData( model, '' ); + expectResult( '
' ); + } ); - model.change( writer => { - writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); - } ); + it( 'should convert on attribute set (other element)', () => { + setModelData( model, '' ); - expectResult( '
' ); + model.change( writer => { + writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert on attribute set (insert new view element)', () => { - setModelData( model, '' ); + expectResult( '
' ); + } ); - model.change( writer => { - writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); - } ); + it( 'should convert on attribute set (insert new view element)', () => { + setModelData( model, '' ); - expectResult( '
' ); + model.change( writer => { + writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert element with slots', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( '
' ); + } ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + - '
' - ); - } ); + it( 'should convert element with slots', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - it( 'should not convert element on adding slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '
' + + '
' + ); + } ); - model.change( writer => { - const slot = writer.createElement( 'slot' ); - const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); - } ); + it( 'should not convert element on adding slot', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + model.change( writer => { + const slot = writer.createElement( 'slot' ); + const paragraph = writer.createElement( 'paragraph' ); + writer.insertText( 'baz', paragraph, 0 ); + writer.insert( paragraph, slot, 0 ); + writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); } ); - it( 'should not convert element on removing slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + + '
' + ); + } ); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - } ); + it( 'should not convert element on removing slot', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); + model.change( writer => { + writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); } ); - it( 'should convert element on a trigger and block atomic converters (remove + attribute)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + expectResult( + '
' + + '
' + + '

bar

' + + '
' + + '
' + ); + } ); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + it( 'should convert element on a trigger and block atomic converters (remove + attribute)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); + model.change( writer => { + writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); } ); - it( 'should convert element on a trigger and block atomic converters (insert + attribute)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); - - model.change( writer => { - writer.insert( modelRoot.getChild( 0 ).getChild( 0 ) ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); - } ); + expectResult( + '
' + + '
' + + '

bar

' + + '
' + + '
' + ); } ); - function getViewAttributes( modelElement ) { - const toStyle = modelElement.hasAttribute( 'toStyle' ) && { style: modelElement.getAttribute( 'toStyle' ) }; - const toClass = modelElement.hasAttribute( 'toClass' ) && { class: 'is-classy' }; - - return { - ...toStyle, - ...toClass - }; - } - - function insertBazSlot( writer, modelRoot ) { - const slot = writer.createElement( 'slot' ); - const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); - } + it( 'should convert element on a trigger and block atomic converters (insert + attribute)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' ); - function* getNodes( childIndex = 0 ) { - const main = viewRoot.getChild( childIndex ); - yield main; + model.change( writer => { + writer.insert( modelRoot.getChild( 0 ).getChild( 0 ) ); + writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + } ); - for ( const { item } of controller.view.createRangeIn( main ) ) { - if ( item.is( 'textProxy' ) ) { - // TreeWalker always create a new instance of a TextProxy so use referenced textNode. - yield item.textNode; - } else { - yield item; - } - } - } + expectResult( + '
' + + '
' + + '

foo

' + + '

bar

' + + '

baz

' + + '
' + + '
' + ); + } ); } ); } ); @@ -3264,6 +3985,38 @@ describe( 'DowncastHelpers', () => { function expectResult( string ) { expect( stringifyView( viewRoot, null, { ignoreRoot: true } ) ).to.equal( string ); } + + function getViewAttributes( modelElement ) { + const toStyle = modelElement.hasAttribute( 'toStyle' ) && { style: modelElement.getAttribute( 'toStyle' ) }; + const toClass = modelElement.hasAttribute( 'toClass' ) && { class: 'is-classy' }; + + return { + ...toStyle, + ...toClass + }; + } + + function insertBazSlot( writer, modelRoot ) { + const slot = writer.createElement( 'slot' ); + const paragraph = writer.createElement( 'paragraph' ); + writer.insertText( 'baz', paragraph, 0 ); + writer.insert( paragraph, slot, 0 ); + writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); + } + + function* getNodes( childIndex = 0 ) { + const main = viewRoot.getChild( childIndex ); + yield main; + + for ( const { item } of controller.view.createRangeIn( main ) ) { + if ( item.is( 'textProxy' ) ) { + // TreeWalker always create a new instance of a TextProxy so use referenced textNode. + yield item.textNode; + } else { + yield item; + } + } + } } ); describe( 'downcast converters', () => { From bbc9e795c1ad096d20068e2d5162030c0b388308 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 1 Sep 2021 16:45:05 +0200 Subject: [PATCH 020/140] Added tests. --- .../src/conversion/downcasthelpers.js | 39 +- .../tests/conversion/downcasthelpers.js | 1896 ++++++----------- 2 files changed, 634 insertions(+), 1301 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 7e52dad816c..cf023e573b5 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -931,15 +931,12 @@ export function insertElement( elementCreator ) { */ export function insertStructure( elementCreator, consumer ) { return ( evt, data, conversionApi ) => { - let debugSlots = false; // eslint-disable-line - // @if CK_DEBUG_SLOTS // debugSlots = true; - const slotsMap = new Map(); // View creation. const viewElement = elementCreator( data.item, { ...conversionApi, - slotFor: createSlotFactory( data.item, slotsMap, conversionApi.writer, { debugSlots } ) + slotFor: createSlotFactory( data.item, slotsMap, conversionApi.writer ) } ); if ( !viewElement ) { @@ -960,10 +957,7 @@ export function insertStructure( elementCreator, consumer ) { conversionApi.writer.insert( viewPosition, viewElement ); // Fill view slots with previous view elements or create new ones. - fillSlots( viewElement, slotsMap, conversionApi, { - reconversion: data.reconversion, - debugSlots - } ); + fillSlots( viewElement, slotsMap, conversionApi, { reconversion: data.reconversion } ); }; } @@ -1962,14 +1956,10 @@ function createConsumer( model ) { // @param {module:engine/model/element~Element} element // @param {Map.>} slotsMap // @param {module:engine/view/downcastwriter~DowncastWriter} writer -// @param {Object} options -// @param {Boolean} [options.debugSlots] // @returns {Function} -function createSlotFactory( element, slotsMap, writer, options ) { +function createSlotFactory( element, slotsMap, writer ) { return modeOrFilter => { - const slot = options.debugSlots ? - writer.createContainerElement( 'div', { 'data-slot': String( modeOrFilter ) } ) : - writer.createContainerElement( '$slot' ); + const slot = writer.createContainerElement( '$slot' ); let children = null; @@ -1983,7 +1973,7 @@ function createSlotFactory( element, slotsMap, writer, options ) { * * @error conversion-slot-mode-unknown */ - throw new CKEditorError( 'conversion-slot-mode-unknown', this, { modeOrFilter } ); + throw new CKEditorError( 'conversion-slot-mode-unknown', null, { modeOrFilter } ); } slotsMap.set( slot, children ); @@ -2026,10 +2016,9 @@ function validateSlotsChildren( element, slotsMap ) { // @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi // @param {Object} options // @param {Boolean} [options.reconversion] -// @param {Boolean} [options.debugSlots] function fillSlots( viewElement, slotsMap, conversionApi, options ) { // Set temporary position mapping to redirect child view elements into a proper slots. - conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'high' } ); + conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'highest' } ); let currentSlot = null; @@ -2039,27 +2028,21 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { reinsertNodes( viewElement, nodes, conversionApi, options ); - if ( !options.debugSlots ) { - conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); - conversionApi.writer.remove( slot ); - } + conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); + conversionApi.writer.remove( slot ); } conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping ); function toViewPositionMapping( evt, data ) { - if ( data.viewPosition || !currentSlot ) { + if ( !currentSlot ) { return; } - const nodeAfter = data.modelPosition.nodeAfter; - - if ( !nodeAfter ) { - return; - } + const element = data.modelPosition.nodeAfter; // Find the proper offset within the slot. - const index = slotsMap.get( currentSlot ).indexOf( nodeAfter ); + const index = slotsMap.get( currentSlot ).indexOf( element ); if ( index < 0 ) { return; diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 3d9efdc6cb6..4cf16b5eac5 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -44,6 +44,8 @@ import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; describe( 'DowncastHelpers', () => { let model, modelRoot, viewRoot, downcastHelpers, controller, modelRootStart; + testUtils.createSinonSandbox(); + beforeEach( () => { model = new Model(); const modelDoc = model.document; @@ -116,676 +118,6 @@ describe( 'DowncastHelpers', () => { expectResult( '

' ); } ); - - describe.skip( 'config.triggerBy', () => { - describe( 'with simple block view structure (without children)', () => { - beforeEach( () => { - model.schema.register( 'simpleBlock', { - allowIn: '$root', - allowAttributes: [ 'toStyle', 'toClass' ] - } ); - downcastHelpers.elementToElement( { - model: 'simpleBlock', - view: ( modelElement, { writer } ) => { - return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); - }, - triggerBy: { - attributes: [ 'toStyle', 'toClass' ] - } - } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'simpleBlock', modelRoot, 0 ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on attribute set', () => { - setModelData( model, '' ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '
' ); - expect( viewAfter ).to.not.equal( viewBefore ); - } ); - - it( 'should convert on attribute change', () => { - setModelData( model, '' ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '
' ); - - expect( viewAfter ).to.not.equal( viewBefore ); - } ); - - it( 'should convert on attribute remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on one attribute add and other remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should properly re-bind mapper mappings and retain markers', () => { - downcastHelpers.elementToElement( { - model: 'simpleBlock', - view: ( modelElement, { writer } ) => { - const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); - - return toWidget( viewElement, writer ); - }, - triggerBy: { - attributes: [ 'toStyle', 'toClass' ] - }, - converterPriority: 'high' - } ); - - const mapper = controller.mapper; - - downcastHelpers.markerToHighlight( { - model: 'myMarker', - view: { classes: 'foo' } - } ); - - setModelData( model, '' ); - - const modelElement = modelRoot.getChild( 0 ); - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.addMarker( 'myMarker', { range: writer.createRangeOn( modelElement ), usingOperation: false } ); - } ); - - expect( mapper.toViewElement( modelElement ) ).to.equal( viewBefore ); - expect( mapper.toModelElement( viewBefore ) ).to.equal( modelElement ); - expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelElement ); - } ); - - const [ viewAfter ] = getNodes(); - - expect( mapper.toViewElement( modelElement ) ).to.equal( viewAfter ); - expect( mapper.toModelElement( viewBefore ) ).to.be.undefined; - expect( mapper.toModelElement( viewAfter ) ).to.equal( modelElement ); - expect( mapper.markerNameToElements( 'myMarker' ).has( viewAfter ) ).to.be.true; - expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.false; - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, '' ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '
' ); - - expect( viewAfter ).to.equal( viewBefore ); - } ); - } ); - - describe( 'with simple block view structure (with children)', () => { - beforeEach( () => { - model.schema.register( 'simpleBlock', { - allowIn: '$root', - allowAttributes: [ 'toStyle', 'toClass' ] - } ); - downcastHelpers.elementToElement( { - model: 'simpleBlock', - view: ( modelElement, { writer } ) => { - return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); - }, - triggerBy: { - attributes: [ 'toStyle', 'toClass' ] - } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'simpleBlock' - } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - const simpleBlock = writer.createElement( 'simpleBlock' ); - const paragraph = writer.createElement( 'paragraph' ); - - writer.insert( simpleBlock, modelRoot, 0 ); - writer.insert( paragraph, simpleBlock, 0 ); - writer.insertText( 'foo', paragraph, 0 ); - } ); - - expectResult( '

foo

' ); - } ); - - it( 'should convert on attribute set', () => { - setModelData( model, 'foo' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter, paraAfter, textAfter ] = getNodes(); - - expectResult( '

foo

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - - it( 'should convert on attribute change', () => { - setModelData( model, 'foo' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter, paraAfter, textAfter ] = getNodes(); - - expectResult( '

foo

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - - it( 'should convert on attribute remove', () => { - setModelData( model, 'foo' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '

foo

' ); - } ); - - it( 'should convert on one attribute add and other remove', () => { - setModelData( model, 'foo' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '

foo

' ); - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, 'foo' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - const [ viewAfter, paraAfter, textAfter ] = getNodes(); - - expectResult( '

foo

' ); - - expect( viewAfter, 'simpleBlock' ).to.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - // TODO - is text always re-converted? - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - } ); - - describe( 'with simple block view structure (with children - reconvert on child add)', () => { - beforeEach( () => { - model.schema.register( 'simpleBlock', { - allowIn: '$root' - } ); - downcastHelpers.elementToElement( { - model: 'simpleBlock', - view: 'div', - triggerBy: { - children: [ 'paragraph' ] - } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'simpleBlock' - } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - const simpleBlock = writer.createElement( 'simpleBlock' ); - const paragraph = writer.createElement( 'paragraph' ); - - writer.insert( simpleBlock, modelRoot, 0 ); - writer.insert( paragraph, simpleBlock, 0 ); - writer.insertText( 'foo', paragraph, 0 ); - } ); - - expectResult( '

foo

' ); - } ); - - it( 'should convert on adding a child (at the beginning)', () => { - setModelData( model, 'foo' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const text = writer.createText( 'bar' ); - - writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); - writer.insert( text, paragraph, 0 ); - } ); - - const [ viewAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter ] = getNodes(); - - expectResult( '

bar

foo

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - - it( 'should convert on adding a child (in the middle)', () => { - setModelData( model, - '' + - 'foobar' + - '' ); - - const [ viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const text = writer.createText( 'baz' ); - - writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); - writer.insert( text, paragraph, 0 ); - } ); - - const [ viewAfter, - paraFooAfter, textFooAfter, /* insertedPara */, /* insertedText */, paraBarAfter, textBarAfter - ] = getNodes(); - - expectResult( '

foo

baz

bar

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); - expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); - expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); - expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); - } ); - - it( 'should convert on adding a child (at the end)', () => { - setModelData( model, 'foo' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const text = writer.createText( 'bar' ); - - writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); - writer.insert( text, paragraph, 0 ); - } ); - - const [ viewAfter, paraAfter, textAfter ] = getNodes(); - - expectResult( '

foo

bar

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - - it( 'should convert on removing a child', () => { - setModelData( model, - 'foobar' ); - - const [ viewBefore, paraBefore, textBefore ] = getNodes(); - - model.change( writer => { - writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) ); - } ); - - const [ viewAfter, paraAfter, textAfter ] = getNodes(); - - expectResult( '

foo

' ); - - expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); - expect( paraAfter, 'para' ).to.equal( paraBefore ); - expect( textAfter, 'text' ).to.equal( textBefore ); - } ); - - // https://github.com/ckeditor/ckeditor5/issues/9641 - it( 'should convert on multiple similar child hooks', () => { - model.schema.register( 'simpleBlock2', { - allowIn: '$root', - allowChildren: 'paragraph' - } ); - downcastHelpers.elementToElement( { - model: 'simpleBlock2', - view: { name: 'div', classes: 'second' }, - triggerBy: { - children: [ 'paragraph' ] - } - } ); - - setModelData( model, - 'foo' + - 'bar' - ); - - const [ viewBefore0, paraBefore0, textBefore0 ] = getNodes( 0 ); - const [ viewBefore1, paraBefore1, textBefore1 ] = getNodes( 1 ); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const text = writer.createText( 'abc' ); - - writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); - writer.insert( text, paragraph, 0 ); - } ); - - const [ viewAfter0, paraAfter0, textAfter0 ] = getNodes( 0 ); - const [ viewAfter1, paraAfter1, textAfter1 ] = getNodes( 1 ); - - expectResult( - '

foo

abc

' + - '

bar

' - ); - - expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); - expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); - expect( textAfter0, 'text' ).to.equal( textBefore0 ); - - expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); - expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); - expect( textAfter1, 'text' ).to.equal( textBefore1 ); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const text = writer.createText( '123' ); - - writer.insert( paragraph, modelRoot.getChild( 1 ), 1 ); - writer.insert( text, paragraph, 0 ); - } ); - - const [ viewAfterAfter0, paraAfterAfter0, textAfterAfter0 ] = getNodes( 0 ); - const [ viewAfterAfter1, paraAfterAfter1, textAfterAfter1 ] = getNodes( 1 ); - - expectResult( - '

foo

abc

' + - '

bar

123

' - ); - - expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); - expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); - expect( textAfter0, 'text' ).to.equal( textBefore0 ); - - expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); - expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); - expect( textAfter1, 'text' ).to.equal( textBefore1 ); - - expect( viewAfterAfter0, 'simpleBlock' ).to.equal( viewAfter0 ); - expect( paraAfterAfter0, 'para' ).to.equal( paraAfter0 ); - expect( textAfterAfter0, 'text' ).to.equal( textAfter0 ); - - expect( viewAfterAfter1, 'simpleBlock' ).to.not.equal( viewAfter1 ); - expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); - expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); - } ); - } ); - - describe( 'with complex view structure - no children allowed', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'toStyle', 'toClass' ] - } ); - downcastHelpers.elementToElement( { - model: 'complex', - view: ( modelElement, { writer } ) => { - const outer = writer.createContainerElement( 'div', { class: 'complex-outer' } ); - const inner = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); - - writer.insert( writer.createPositionAt( outer, 0 ), inner ); - - return outer; - }, - triggerBy: { - attributes: [ 'toStyle', 'toClass' ] - } - } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on attribute set', () => { - setModelData( model, '' ); - - const [ outerDivBefore, innerDivBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const [ outerDivAfter, innerDivAfter ] = getNodes(); - - expectResult( '
' ); - expect( outerDivAfter, 'outer div' ).to.not.equal( outerDivBefore ); - expect( innerDivAfter, 'inner div' ).to.not.equal( innerDivBefore ); - } ); - - it( 'should convert on attribute change', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on attribute remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on one attribute add and other remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, '' ); - - const [ outerDivBefore, innerDivBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - const [ outerDivAfter, innerDivAfter ] = getNodes(); - - expectResult( '
' ); - - expect( outerDivAfter, 'outer div' ).to.equal( outerDivBefore ); - expect( innerDivAfter, 'inner div' ).to.equal( innerDivBefore ); - } ); - } ); - - describe( 'with complex view structure (without slots)', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'toStyle', 'toClass' ] - } ); - downcastHelpers.elementToElement( { - model: 'complex', - view: ( modelElement, { writer, mapper } ) => { - const outer = writer.createContainerElement( 'c-outer' ); - const inner = writer.createContainerElement( 'c-inner', getViewAttributes( modelElement ) ); - - writer.insert( writer.createPositionAt( outer, 0 ), inner ); - mapper.bindElements( modelElement, outer ); // Need for nested mapping - mapper.bindElements( modelElement, inner ); - - return outer; - }, - triggerBy: { - attributes: [ 'toStyle', 'toClass' ] - } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'complex' - } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); - - expectResult( '' ); - } ); - - it( 'should convert on attribute set', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - it( 'should convert on attribute remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - it( 'should convert on one attribute add and other remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - describe( 'memoization', () => { - it( 'should create new element on re-converting element', () => { - setModelData( model, '' ); - - const [ outerBefore, innerBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const [ outerAfter, innerAfter ] = getNodes(); - - expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); - expect( innerAfter, 'inner' ).to.not.equal( innerBefore ); - } ); - - // Skipped, as it would require two-level mapping. See https://github.com/ckeditor/ckeditor5/issues/1589. - // Doable as a similar case works in table scenario for table cells (table is refreshed). - it.skip( 'should not re-create child elements on re-converting element', () => { - setModelData( model, 'Foo bar baz' ); - - expectResult( '

Foo bar baz

' ); - const renderedViewView = viewRoot.getChild( 0 ).getChild( 0 ); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const viewAfterReRender = viewRoot.getChild( 0 ).getChild( 0 ); - - expect( viewAfterReRender ).to.equal( renderedViewView ); - } ); - } ); - } ); - } ); } ); describe( 'elementToStructure()', () => { @@ -843,7 +175,82 @@ describe( 'DowncastHelpers', () => { expectResult( '

' ); } ); - describe( 'with simple block view structure (without children)', () => { + it( 'config.view is a function that does not return view element', () => { + downcastHelpers.elementToStructure( { + model: 'heading', + view: () => null + } ); + + model.change( writer => { + writer.insertElement( 'heading', {}, modelRoot, 0 ); + } ); + + expectResult( '' ); + } ); + + describe( 'converting element together with selected attributes', () => { + it( 'should allow passing a list of attributes to convert and consume', () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + + let consumable; + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + }, { priority: 'low' } ); + + setModelData( model, '' ); + + expectResult( '
' ); + + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toStyle' ) ).to.be.false; + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toClass' ) ).to.be.null; + } ); + + it( 'should allow passing a single attribute name to convert and consume', () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simpleBlock', + attributes: 'toStyle' + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + + let consumable; + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + }, { priority: 'low' } ); + + setModelData( model, '' ); + + expectResult( '
' ); + + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toStyle' ) ).to.be.false; + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toClass' ) ).to.be.null; + } ); + } ); + + describe( 'with simple block view structure (without slots, without reconvert on children list change)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -862,6 +269,10 @@ describe( 'DowncastHelpers', () => { } ); it( 'should convert on insert', () => { + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + model.change( writer => { writer.insertElement( 'simpleBlock', modelRoot, 0 ); } ); @@ -872,6 +283,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute set', () => { setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + const [ viewBefore ] = getNodes(); model.change( writer => { @@ -887,6 +302,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute change', () => { setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + const [ viewBefore ] = getNodes(); model.change( writer => { @@ -903,6 +322,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute remove', () => { setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + model.change( writer => { writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); } ); @@ -913,6 +336,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on one attribute add and other remove', () => { setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + model.change( writer => { writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); @@ -968,9 +395,13 @@ describe( 'DowncastHelpers', () => { expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.false; } ); - it( 'should do nothing if non-triggerBy attribute has changed', () => { + it( 'should not reconvert if non watched attribute has changed', () => { setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + const [ viewBefore ] = getNodes(); model.change( writer => { @@ -983,9 +414,39 @@ describe( 'DowncastHelpers', () => { expect( viewAfter ).to.equal( viewBefore ); } ); + + it( 'should not reconvert on child element added', () => { + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '

' ); + + expect( viewAfter ).to.equal( viewBefore ); + } ); } ); - describe( 'with simple block view structure (with children)', () => { + describe( 'with simple block view structure (with slots, without reconvert on children list change)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -1018,6 +479,10 @@ describe( 'DowncastHelpers', () => { } ); it( 'should convert on insert', () => { + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + model.change( writer => { const simpleBlock = writer.createElement( 'simpleBlock' ); const paragraph = writer.createElement( 'paragraph' ); @@ -1033,6 +498,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute set', () => { setModelData( model, 'foo' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { @@ -1051,6 +520,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute change', () => { setModelData( model, 'foo' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { @@ -1069,6 +542,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on attribute remove', () => { setModelData( model, 'foo' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + model.change( writer => { writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); } ); @@ -1079,6 +556,10 @@ describe( 'DowncastHelpers', () => { it( 'should convert on one attribute add and other remove', () => { setModelData( model, 'foo' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + model.change( writer => { writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); @@ -1087,9 +568,13 @@ describe( 'DowncastHelpers', () => { expectResult( '

foo

' ); } ); - it( 'should do nothing if non-triggerBy attribute has changed', () => { + it( 'should not reconvert if non watched attribute has changed', () => { setModelData( model, 'foo' ); + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { @@ -1102,12 +587,31 @@ describe( 'DowncastHelpers', () => { expect( viewAfter, 'simpleBlock' ).to.equal( viewBefore ); expect( paraAfter, 'para' ).to.equal( paraBefore ); - // TODO - is text always re-converted? expect( textAfter, 'text' ).to.equal( textBefore ); } ); + + it( 'should not reconvert on child element added', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '

' ); + + expect( viewAfter ).to.equal( viewBefore ); + } ); } ); - describe( 'with simple block view structure (with children - reconvert on child add)', () => { + describe( 'with simple block view structure (reconvert on child add)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root' @@ -1139,6 +643,10 @@ describe( 'DowncastHelpers', () => { } ); it( 'should convert on insert', () => { + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + model.change( writer => { const simpleBlock = writer.createElement( 'simpleBlock' ); const paragraph = writer.createElement( 'paragraph' ); @@ -1151,8 +659,15 @@ describe( 'DowncastHelpers', () => { expectResult( '

foo

' ); } ); - it( 'should convert on adding a child (at the beginning)', () => { - setModelData( model, 'foo' ); + it( 'should convert on adding a child (at the beginning)', () => { + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); const [ viewBefore, paraBefore, textBefore ] = getNodes(); @@ -1171,13 +686,23 @@ describe( 'DowncastHelpers', () => { expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); expect( paraAfter, 'para' ).to.equal( paraBefore ); expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); it( 'should convert on adding a child (in the middle)', () => { setModelData( model, - '' + - 'foobar' + - '' ); + '' + + 'foo' + + 'bar' + + '' + ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); const [ viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); @@ -1200,10 +725,18 @@ describe( 'DowncastHelpers', () => { expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + expect( spy.called ).to.be.true; } ); it( 'should convert on adding a child (at the end)', () => { - setModelData( model, 'foo' ); + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); const [ viewBefore, paraBefore, textBefore ] = getNodes(); @@ -1222,12 +755,20 @@ describe( 'DowncastHelpers', () => { expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); expect( paraAfter, 'para' ).to.equal( paraBefore ); expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); it( 'should convert on removing a child', () => { setModelData( model, 'foobar' ); + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { @@ -1241,6 +782,7 @@ describe( 'DowncastHelpers', () => { expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); expect( paraAfter, 'para' ).to.equal( paraBefore ); expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); // https://github.com/ckeditor/ckeditor5/issues/9641 @@ -1249,6 +791,7 @@ describe( 'DowncastHelpers', () => { allowIn: '$root', allowChildren: 'paragraph' } ); + downcastHelpers.elementToStructure( { model: { name: 'simpleBlock2', @@ -1264,8 +807,8 @@ describe( 'DowncastHelpers', () => { } ); setModelData( model, - 'foo' + - 'bar' + 'foo' + + 'bar' ); const [ viewBefore0, paraBefore0, textBefore0 ] = getNodes( 0 ); @@ -1329,7 +872,7 @@ describe( 'DowncastHelpers', () => { } ); } ); - describe( 'with complex view structure - no children allowed', () => { + describe( 'with complex view structure - single slot for all children', () => { beforeEach( () => { model.schema.register( 'complex', { allowIn: '$root', @@ -1339,30 +882,57 @@ describe( 'DowncastHelpers', () => { downcastHelpers.elementToStructure( { model: { name: 'complex', - attributes: [ 'toStyle', 'toClass' ] + attributes: [ 'toStyle', 'toClass' ], + children: true }, - view: ( modelElement, { writer } ) => { + view: ( modelElement, { writer, slotFor } ) => { const outer = writer.createContainerElement( 'div', { class: 'complex-outer' } ); const inner = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); writer.insert( writer.createPositionAt( outer, 0 ), inner ); + writer.insert( writer.createPositionAt( inner, 0 ), slotFor( 'children' ) ); return outer; } } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'complex' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); } ); it( 'should convert on insert', () => { + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + spy(); + } ); + model.change( writer => { writer.insertElement( 'complex', modelRoot, 0 ); } ); expectResult( '
' ); + expect( spy.called ).to.be.true; } ); it( 'should convert on attribute set', () => { setModelData( model, '' ); + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + const [ outerDivBefore, innerDivBefore ] = getNodes(); model.change( writer => { @@ -1374,744 +944,532 @@ describe( 'DowncastHelpers', () => { expectResult( '
' ); expect( outerDivAfter, 'outer div' ).to.not.equal( outerDivBefore ); expect( innerDivAfter, 'inner div' ).to.not.equal( innerDivBefore ); + expect( spy.called ).to.be.true; } ); it( 'should convert on attribute change', () => { setModelData( model, '' ); + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + model.change( writer => { writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); } ); expectResult( '
' ); + expect( spy.called ).to.be.true; } ); it( 'should convert on attribute remove', () => { setModelData( model, '' ); - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on one attribute add and other remove', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, '' ); - - const [ outerDivBefore, innerDivBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - const [ outerDivAfter, innerDivAfter ] = getNodes(); - - expectResult( '
' ); - - expect( outerDivAfter, 'outer div' ).to.equal( outerDivBefore ); - expect( innerDivAfter, 'inner div' ).to.equal( innerDivBefore ); - } ); - } ); - - describe( 'with complex view structure (without slots)', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'toStyle', 'toClass' ] - } ); - - downcastHelpers.elementToStructure( { - model: { - name: 'complex', - attributes: [ 'toStyle', 'toClass' ] - }, - view: ( modelElement, { writer, slotFor } ) => { - const outer = writer.createContainerElement( 'c-outer' ); - const inner = writer.createContainerElement( 'c-inner', getViewAttributes( modelElement ) ); - - writer.insert( writer.createPositionAt( outer, 0 ), inner ); - writer.insert( writer.createPositionAt( inner, 0 ), slotFor( 'children' ) ); - - return outer; - } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'complex' - } ); - - downcastHelpers.elementToElement( { - model: 'paragraph', - view: 'p' - } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); - - expectResult( '' ); - } ); - - it( 'should convert on attribute set', () => { - setModelData( model, '' ); + const spy = sinon.spy(); - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); } ); - expectResult( '' ); - } ); - - it( 'should convert on attribute remove', () => { - setModelData( model, '' ); - model.change( writer => { writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); } ); - expectResult( '' ); + expectResult( '
' ); + expect( spy.called ).to.be.true; } ); it( 'should convert on one attribute add and other remove', () => { setModelData( model, '' ); - model.change( writer => { - writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); - writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - it( 'should do nothing if non-triggerBy attribute has changed', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '' ); - } ); - - describe( 'memoization', () => { - it( 'should create new element on re-converting element', () => { - setModelData( model, '' ); - - const [ outerBefore, innerBefore ] = getNodes(); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const [ outerAfter, innerAfter ] = getNodes(); - - expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); - expect( innerAfter, 'inner' ).to.not.equal( innerBefore ); - } ); - - // Skipped, as it would require two-level mapping. See https://github.com/ckeditor/ckeditor5/issues/1589. - // Doable as a similar case works in table scenario for table cells (table is refreshed). - it.skip( 'should not re-create child elements on re-converting element', () => { - setModelData( model, 'Foo bar baz' ); - - expectResult( '

Foo bar baz

' ); - const renderedViewView = viewRoot.getChild( 0 ).getChild( 0 ); - - model.change( writer => { - writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); - } ); - - const viewAfterReRender = viewRoot.getChild( 0 ).getChild( 0 ); - - expect( viewAfterReRender ).to.equal( renderedViewView ); - } ); - } ); - } ); - - describe( 'with complex view structure (single slot for all child nodes)', () => { - beforeEach( () => { - model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] - } ); - - downcastHelpers.elementToStructure( { - model: { - name: 'complex', - attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ], - children: true - }, - view: ( modelElement, { writer, slotFor } ) => { - const classForMain = !!modelElement.getAttribute( 'classForMain' ); - const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); - const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); - - const outer = writer.createContainerElement( 'div', { - class: `complex-slots${ classForMain ? ' with-class' : '' }` - } ); - - const inner = writer.createContainerElement( 'div', { - class: `slots${ classForWrap ? ' with-class' : '' }` - } ); - - if ( attributeToElement ) { - const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); - - writer.insert( writer.createPositionAt( outer, 0 ), optional ); - } - - writer.insert( writer.createPositionAt( outer, 'end' ), inner ); - writer.insert( writer.createPositionAt( inner, 0 ), slotFor( 'children' ) ); - - return outer; - } - } ); - - model.schema.register( 'slot', { - allowIn: 'complex' - } ); - - downcastHelpers.elementToElement( { - model: 'slot', - view: { name: 'div', classes: 'slot' } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'slot' - } ); - - downcastHelpers.elementToElement( { - model: 'paragraph', - view: 'p' - } ); - } ); - - it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on attribute set (main element)', () => { - setModelData( model, '' ); - - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); - - expectResult( '
' ); - } ); - - it( 'should convert on attribute set (other element)', () => { - setModelData( model, '' ); + const spy = sinon.spy(); - model.change( writer => { - writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); } ); - expectResult( '
' ); - } ); - - it( 'should convert on attribute set (insert new view element)', () => { - setModelData( model, '' ); - model.change( writer => { - writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); } ); - expectResult( '
' ); - } ); - - it( 'should convert element with slots', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); - - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + - '
' - ); + expectResult( '
' ); + expect( spy.called ).to.be.true; } ); - it( 'should convert element on adding slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); - - model.change( writer => { - insertBazSlot( writer, modelRoot ); - } ); - - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); - } ); + it( 'should convert on adding a child (at the beginning)', () => { + setModelData( model, 'foo' ); - it( 'should convert element on removing slot', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + const spy = sinon.spy(); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); } ); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); - } ); - - it( 'should convert element on multiple triggers (remove + insert)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + const [ outerBefore, viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - insertBazSlot( writer, modelRoot ); + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); + writer.insert( text, paragraph, 0 ); } ); - expectResult( - '
' + - '
' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + const [ outerAfter, viewAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter ] = getNodes(); + + expectResult( '

bar

foo

' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( viewAfter, 'inner' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); - it( 'should convert element on multiple triggers (remove + attribute)', () => { + it( 'should convert on adding a child (in the middle)', () => { setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + '' + + 'foo' + + 'bar' + + '' + ); - model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + const spy = sinon.spy(); - expectResult( - '
' + - '
' + - '

bar

' + - '
' + - '
' - ); - } ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); - it( 'should convert element on multiple triggers (insert + attribute)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + const [ outerBefore, viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); model.change( writer => { - insertBazSlot( writer, modelRoot ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'baz' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); } ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); + const [ outerAfter, viewAfter, + paraFooAfter, textFooAfter, /* insertedPara */, /* insertedText */, paraBarAfter, textBarAfter + ] = getNodes(); + + expectResult( '

foo

baz

bar

' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( viewAfter, 'inner' ).to.not.equal( viewBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); + expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + expect( spy.called ).to.be.true; } ); - it( 'should not trigger refresh on adding a slot to an element without triggerBy conversion', () => { - model.schema.register( 'other', { - allowIn: '$root' - } ); - model.schema.extend( 'slot', { - allowIn: 'other' - } ); - downcastHelpers.elementToElement( { - model: 'other', - view: { - name: 'div', - classes: 'other' - } - } ); - downcastHelpers.elementToElement( { - model: 'slot', - view: { - name: 'div', - classes: 'slot' - } + it( 'should convert on adding a child (at the end)', () => { + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); } ); - setModelData( model, - '' + - 'foo' + - 'bar' + - '' - ); - const otherView = viewRoot.getChild( 0 ); + const [ outerBefore, viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { - insertBazSlot( writer, modelRoot ); + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); } ); - expectResult( - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' - ); - const otherViewAfter = viewRoot.getChild( 0 ); + const [ outerAfter, viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

bar

' ); - expect( otherView, 'the view should not be refreshed' ).to.equal( otherViewAfter ); + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( viewAfter, 'inner' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); - describe( 'memoization', () => { - it( 'should create new element on re-converting element', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); + it( 'should convert on removing a child', () => { + setModelData( model, 'foobar' ); - const [ complexView ] = getNodes(); + const spy = sinon.spy(); - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); - const [ viewAfterReRender ] = getNodes(); + const [ outerBefore, viewBefore, paraBefore, textBefore ] = getNodes(); - expect( viewAfterReRender, 'the view should be refreshed' ).to.not.equal( complexView ); + model.change( writer => { + writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) ); } ); - it( 'should not re-create slot\'s child elements on re-converting main element (attribute changed)', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); + const [ outerAfter, viewAfter, paraAfter, textAfter ] = getNodes(); - const [ main, /* unused */, - slotOne, paraOne, textNodeOne, - slotTwo, paraTwo, textNodeTwo ] = getNodes(); + expectResult( '

foo

' ); - model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); - } ); + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( viewAfter, 'inner' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; + } ); + + it( 'should not reconvert if non watched attribute has changed', () => { + setModelData( model, '' ); - const [ mainAfter, /* unused */, - slotOneAfter, paraOneAfter, textNodeOneAfter, - slotTwoAfter, paraTwoAfter, textNodeTwoAfter ] = getNodes(); + const spy = sinon.spy(); - expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); - expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); - expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); - expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); - expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); + controller.downcastDispatcher.on( 'insert:complex', () => { + spy(); } ); - it( 'should not re-create slot\'s child elements on re-converting main element (slot added)', () => { - setModelData( model, '' + - 'foo' + - 'bar' + - '' - ); + const [ outerDivBefore, innerDivBefore ] = getNodes(); - const [ main, /* unused */, - slotOne, paraOne, textNodeOne, - slotTwo, paraTwo, textNodeTwo ] = getNodes(); + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); - model.change( writer => { - const slot = writer.createElement( 'slot' ); - const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); - } ); + const [ outerDivAfter, innerDivAfter ] = getNodes(); - const [ mainAfter, /* unused */, - slotOneAfter, paraOneAfter, textNodeOneAfter, - slotTwoAfter, paraTwoAfter, textNodeTwoAfter, - slotThreeAfter, paraThreeAfter, textNodeThreeAfter - ] = getNodes(); + expectResult( '
' ); - expect( mainAfter, 'main view' ).to.not.equal( main ); - expect( slotOneAfter, 'first slot view' ).to.equal( slotOne ); - expect( slotTwoAfter, 'second slot view' ).to.equal( slotTwo ); - expect( paraOneAfter, 'first slot paragraph view' ).to.equal( paraOne ); - expect( textNodeOneAfter, 'first slot text node view' ).to.equal( textNodeOne ); - expect( paraTwoAfter, 'second slot paragraph view' ).to.equal( paraTwo ); - expect( textNodeTwoAfter, 'second slot text node view' ).to.equal( textNodeTwo ); - expect( slotThreeAfter, 'third slot view' ).to.not.be.undefined; - expect( paraThreeAfter, 'third slot paragraph view' ).to.not.be.undefined; - expect( textNodeThreeAfter, 'third slot text node view' ).to.not.be.undefined; - } ); + expect( outerDivAfter, 'outer div' ).to.equal( outerDivBefore ); + expect( innerDivAfter, 'inner div' ).to.equal( innerDivBefore ); + expect( spy.notCalled ).to.be.true; } ); } ); - // Skipped, as it would require two-level mapping. - describe.skip( 'with complex view structure (slot conversion atomic converters for some changes)', () => { + describe( 'with complex view structure - multiple slots', () => { beforeEach( () => { model.schema.register( 'complex', { - allowIn: '$root', - allowAttributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] + allowIn: '$root' } ); - function createViewSlot( slot, { writer, mapper } ) { - const viewSlot = writer.createContainerElement( 'div', { class: 'slot' } ); - - mapper.bindElements( slot, viewSlot ); - - return viewSlot; - } - - downcastHelpers.elementToElement( { - model: 'complex', - view: ( modelElement, { writer, mapper, consumable } ) => { - const classForMain = !!modelElement.getAttribute( 'classForMain' ); - const classForWrap = !!modelElement.getAttribute( 'classForWrap' ); - const attributeToElement = !!modelElement.getAttribute( 'attributeToElement' ); - - const outer = writer.createContainerElement( 'div', { - class: `complex-slots${ classForMain ? ' with-class' : '' }` - } ); - const inner = writer.createContainerElement( 'div', { - class: `slots${ classForWrap ? ' with-class' : '' }` - } ); - - if ( attributeToElement ) { - const optional = writer.createEmptyElement( 'div', { class: 'optional' } ); - writer.insert( writer.createPositionAt( outer, 0 ), optional ); - } - - writer.insert( writer.createPositionAt( outer, 'end' ), inner ); - mapper.bindElements( modelElement, outer ); - mapper.bindElements( modelElement, inner ); + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const outer = writer.createContainerElement( 'div', { class: 'complex-outer' } ); + const inner1 = writer.createContainerElement( 'div', { class: 'inner-first' } ); + const inner2 = writer.createContainerElement( 'div', { class: 'inner-second' } ); - for ( const slot of modelElement.getChildren() ) { - const viewSlot = createViewSlot( slot, { writer, mapper } ); + writer.insert( writer.createPositionAt( outer, 'end' ), inner1 ); + writer.insert( writer.createPositionAt( outer, 'end' ), inner2 ); - writer.insert( writer.createPositionAt( inner, slot.index ), viewSlot ); - consumable.consume( slot, 'insert' ); - } + writer.insert( writer.createPositionAt( inner1, 0 ), slotFor( element => element.index < 2 ) ); + writer.insert( writer.createPositionAt( inner2, 0 ), slotFor( element => element.index >= 2 ) ); return outer; - }, - triggerBy: { - attributes: [ 'classForMain', 'classForWrap', 'attributeToElement' ] - // Contrary to the previous test - do not act on child changes. - // children: [ 'slot' ] } } ); - downcastHelpers.elementToElement( { - model: 'slot', - view: createViewSlot - } ); - model.schema.register( 'slot', { + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', allowIn: 'complex' } ); - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'slot' + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' } ); - downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); } ); it( 'should convert on insert', () => { - model.change( writer => { - writer.insertElement( 'complex', modelRoot, 0 ); - } ); - - expectResult( '
' ); - } ); + const spy = sinon.spy(); - it( 'should convert on attribute set (main element)', () => { - setModelData( model, '' ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + spy(); + } ); model.change( writer => { - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + writer.insertElement( 'complex', modelRoot, 0 ); } ); - expectResult( '
' ); + expectResult( '
' ); + expect( spy.called ).to.be.true; } ); - it( 'should convert on attribute set (other element)', () => { - setModelData( model, '' ); + it( 'should convert on adding a child (at the beginning)', () => { + setModelData( model, 'foo' ); - model.change( writer => { - writer.setAttribute( 'classForWrap', true, modelRoot.getChild( 0 ) ); - } ); + const spy = sinon.spy(); - expectResult( '
' ); - } ); + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); - it( 'should convert on attribute set (insert new view element)', () => { - setModelData( model, '' ); + const [ outerBefore, firstBefore, paraBefore, textBefore, secondBefore ] = getNodes(); model.change( writer => { - writer.setAttribute( 'attributeToElement', true, modelRoot.getChild( 0 ) ); - } ); + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); - expectResult( '
' ); - } ); + writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); + writer.insert( text, paragraph, 0 ); + } ); - it( 'should convert element with slots', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + const [ outerAfter, firstAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter, secondAfter ] = getNodes(); expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '
' + + '
' + + '

bar

foo

' + + '
' + '
' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( firstAfter, 'inner first' ).to.not.equal( firstBefore ); + expect( secondAfter, 'inner second' ).to.not.equal( secondBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; } ); - it( 'should not convert element on adding slot', () => { + it( 'should convert on adding a child (in the middle)', () => { setModelData( model, '' + - 'foo' + - 'bar' + - '' ); + 'foo' + + 'bar' + + '' + ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ + outerBefore, + firstBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore, + secondBefore + ] = getNodes(); model.change( writer => { - const slot = writer.createElement( 'slot' ); const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); + const text = writer.createText( 'baz' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); } ); + const [ + outerAfter, + firstAfter, paraFooAfter, textFooAfter, /* insertedPara */, /* insertedText */, + secondAfter, paraBarAfter, textBarAfter + ] = getNodes(); + expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + + '
' + + '

foo

baz

' + + '

bar

' + '
' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( firstAfter, 'inner first' ).to.not.equal( firstBefore ); + expect( secondAfter, 'inner second' ).to.not.equal( secondBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); + expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + expect( spy.called ).to.be.true; } ); - it( 'should not convert element on removing slot', () => { + it( 'should convert on adding a child (at the end)', () => { setModelData( model, '' + - 'foo' + - 'bar' + - '' ); + 'foo' + + 'bar' + + '' + ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ + outerBefore, + firstBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore, + secondBefore + ] = getNodes(); model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'baz' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 'end' ); + writer.insert( text, paragraph, 0 ); } ); + const [ + outerAfter, + firstAfter, paraFooAfter, textFooAfter, paraBarAfter, textBarAfter, + secondAfter, /* insertedPara */, /* insertedText */ + ] = getNodes(); + expectResult( - '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

foo

bar

' + + '

baz

' + '
' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( firstAfter, 'inner first' ).to.not.equal( firstBefore ); + expect( secondAfter, 'inner second' ).to.not.equal( secondBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); + expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + expect( spy.called ).to.be.true; } ); - it( 'should convert element on a trigger and block atomic converters (remove + attribute)', () => { + it( 'should convert on removing a child', () => { setModelData( model, '' + - 'foo' + - 'bar' + - '' ); + 'foo' + + 'bar' + + 'baz' + + 'abc' + + '' + ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:complex', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ + outerBefore, + firstBefore, paraFooBefore, textFooBefore, /* removedPara */, /* removedText */, + secondBefore, paraBazBefore, textBazBefore, paraAbcBefore, textAbcBefore + ] = getNodes(); model.change( writer => { - writer.remove( modelRoot.getChild( 0 ).getChild( 0 ) ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) ); } ); + const [ + outerAfter, + firstAfter, paraFooAfter, textFooAfter, paraBazAfter, textBazAfter, + secondAfter, paraAbcAfter, textAbcAfter + ] = getNodes(); + expectResult( - '
' + - '
' + - '

bar

' + - '
' + + '
' + + '

foo

baz

' + + '

abc

' + '
' ); + + expect( outerAfter, 'outer' ).to.not.equal( outerBefore ); + expect( firstAfter, 'inner first' ).to.not.equal( firstBefore ); + expect( secondAfter, 'inner second' ).to.not.equal( secondBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBazAfter, 'para baz' ).to.equal( paraBazBefore ); + expect( textBazAfter, 'text baz' ).to.equal( textBazBefore ); + expect( paraAbcAfter, 'para abc' ).to.equal( paraAbcBefore ); + expect( textAbcAfter, 'text abc' ).to.equal( textAbcBefore ); + expect( spy.called ).to.be.true; } ); + } ); - it( 'should convert element on a trigger and block atomic converters (insert + attribute)', () => { - setModelData( model, - '' + - 'foo' + - 'bar' + - '' ); + it( 'should throw an exception if invalid slot mode or filter was provided', () => { + model.schema.register( 'simple', { + allowIn: '$root' + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simple', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const element = writer.createContainerElement( 'div' ); + + writer.insert( writer.createPositionAt( element, 0 ), slotFor( 'foo' ) ); + + return element; + } + } ); + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simple' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + expectToThrowCKEditorError( () => { model.change( writer => { - writer.insert( modelRoot.getChild( 0 ).getChild( 0 ) ); - writer.setAttribute( 'classForMain', true, modelRoot.getChild( 0 ) ); + const simple = writer.createElement( 'simple' ); + const paragraph = writer.createElement( 'paragraph' ); + + writer.insert( paragraph, simple, 0 ); + writer.insert( simple, modelRoot, 0 ); } ); + }, /^conversion-slot-mode-unknown/, null, { modeOrFilter: 'foo' } ); - expectResult( - '
' + - '
' + - '

foo

' + - '

bar

' + - '

baz

' + - '
' + - '
' - ); - } ); + // TODO add context instead of null } ); + + // it( 'should throw an exception if invalid slot mode or filrer was provided', () => { + // model.schema.register( 'complex', { + // allowIn: '$root' + // } ); + // + // downcastHelpers.elementToStructure( { + // model: { + // name: 'complex', + // children: true + // }, + // view: ( modelElement, { writer, slotFor } ) => { + // const outer = writer.createContainerElement( 'div' ); + // const inner1 = writer.createContainerElement( 'div', { class: 'inner-first' } ); + // const inner2 = writer.createContainerElement( 'div', { class: 'inner-second' } ); + // + // writer.insert( writer.createPositionAt( outer, 'end' ), inner1 ); + // writer.insert( writer.createPositionAt( outer, 'end' ), inner2 ); + // + // writer.insert( writer.createPositionAt( inner1, 0 ), slotFor( element => element.index < 2 ) ); + // writer.insert( writer.createPositionAt( inner2, 0 ), slotFor( element => element.index >= 2 ) ); + // + // return outer; + // } + // } ); + // } ); } ); describe( 'attributeToElement()', () => { @@ -3996,14 +3354,6 @@ describe( 'DowncastHelpers', () => { }; } - function insertBazSlot( writer, modelRoot ) { - const slot = writer.createElement( 'slot' ); - const paragraph = writer.createElement( 'paragraph' ); - writer.insertText( 'baz', paragraph, 0 ); - writer.insert( paragraph, slot, 0 ); - writer.insert( slot, modelRoot.getChild( 0 ), 'end' ); - } - function* getNodes( childIndex = 0 ) { const main = viewRoot.getChild( childIndex ); yield main; From 2e0f9973d92f466341a002f9822f00a816e5e6ba Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 1 Sep 2021 19:14:55 +0200 Subject: [PATCH 021/140] Added tests. --- .../src/conversion/downcasthelpers.js | 57 +++--- .../tests/conversion/downcasthelpers.js | 170 +++++++++++++++--- 2 files changed, 171 insertions(+), 56 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index cf023e573b5..5f6e4f5ce83 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -936,7 +936,7 @@ export function insertStructure( elementCreator, consumer ) { // View creation. const viewElement = elementCreator( data.item, { ...conversionApi, - slotFor: createSlotFactory( data.item, slotsMap, conversionApi.writer ) + slotFor: createSlotFactory( data.item, slotsMap, conversionApi ) } ); if ( !viewElement ) { @@ -944,7 +944,7 @@ export function insertStructure( elementCreator, consumer ) { } // Check if all children are covered by slots and there is no child that landed in multiple slots. - validateSlotsChildren( data.item, slotsMap ); + validateSlotsChildren( data.item, slotsMap, conversionApi ); // Consume an element insertion and all present attributes that are specified as a reconversion triggers. if ( !consumer( data.item, conversionApi.consumable ) ) { @@ -1272,7 +1272,7 @@ function changeAttribute( attributeCreator ) { * * @error conversion-attribute-to-attribute-on-text */ - throw new CKEditorError( 'conversion-attribute-to-attribute-on-text', this, data ); + throw new CKEditorError( 'conversion-attribute-to-attribute-on-text', conversionApi.dispatcher, data ); } // First remove the old attribute if there was one. @@ -1955,11 +1955,11 @@ function createConsumer( model ) { // // @param {module:engine/model/element~Element} element // @param {Map.>} slotsMap -// @param {module:engine/view/downcastwriter~DowncastWriter} writer +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi // @returns {Function} -function createSlotFactory( element, slotsMap, writer ) { +function createSlotFactory( element, slotsMap, conversionApi ) { return modeOrFilter => { - const slot = writer.createContainerElement( '$slot' ); + const slot = conversionApi.writer.createContainerElement( '$slot' ); let children = null; @@ -1973,7 +1973,7 @@ function createSlotFactory( element, slotsMap, writer ) { * * @error conversion-slot-mode-unknown */ - throw new CKEditorError( 'conversion-slot-mode-unknown', null, { modeOrFilter } ); + throw new CKEditorError( 'conversion-slot-mode-unknown', conversionApi.dispatcher, { modeOrFilter } ); } slotsMap.set( slot, children ); @@ -1986,7 +1986,8 @@ function createSlotFactory( element, slotsMap, writer ) { // // @param {module:engine/model/element~Element} // @param {Map.>} slotsMap -function validateSlotsChildren( element, slotsMap ) { +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +function validateSlotsChildren( element, slotsMap, conversionApi ) { const childrenInSlots = Array.from( slotsMap.values() ).flat(); const uniqueChildrenInSlots = new Set( childrenInSlots ); @@ -1996,7 +1997,7 @@ function validateSlotsChildren( element, slotsMap ) { * * @error conversion-slot-filter-to-permissive */ - throw new CKEditorError( 'conversion-slot-filter-to-permissive', this, { element, slotsMap } ); + throw new CKEditorError( 'conversion-slot-filter-to-permissive', conversionApi.dispatcher, { element, slotsMap } ); } if ( uniqueChildrenInSlots.size != element.childCount ) { @@ -2005,7 +2006,7 @@ function validateSlotsChildren( element, slotsMap ) { * * @error conversion-slot-filter-to-restrictive */ - throw new CKEditorError( 'conversion-slot-filter-to-restrictive', this, { element, slotsMap } ); + throw new CKEditorError( 'conversion-slot-filter-to-restrictive', conversionApi.dispatcher, { element, slotsMap } ); } } @@ -2021,28 +2022,26 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'highest' } ); let currentSlot = null; + let currentSlotNodes = null; // Fill slots with nested view nodes. - for ( const [ slot, nodes ] of slotsMap ) { - currentSlot = slot; - - reinsertNodes( viewElement, nodes, conversionApi, options ); + for ( [ currentSlot, currentSlotNodes ] of slotsMap ) { + reinsertNodes( viewElement, currentSlotNodes, conversionApi, options ); - conversionApi.writer.move( conversionApi.writer.createRangeIn( slot ), conversionApi.writer.createPositionBefore( slot ) ); - conversionApi.writer.remove( slot ); + conversionApi.writer.move( + conversionApi.writer.createRangeIn( currentSlot ), + conversionApi.writer.createPositionBefore( currentSlot ) + ); + conversionApi.writer.remove( currentSlot ); } conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping ); function toViewPositionMapping( evt, data ) { - if ( !currentSlot ) { - return; - } - const element = data.modelPosition.nodeAfter; // Find the proper offset within the slot. - const index = slotsMap.get( currentSlot ).indexOf( element ); + const index = currentSlotNodes.indexOf( element ); if ( index < 0 ) { return; @@ -2061,17 +2060,19 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { // @param {Object} options // @param {Boolean} [options.reconversion] function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { + const { writer, mapper, consumable } = conversionApi; + // Fill with nested view nodes. for ( const modelChildNode of modelNodes ) { - const viewChildNode = conversionApi.mapper.toViewElement( modelChildNode ); + const viewChildNode = mapper.toViewElement( modelChildNode ); if ( options.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { - if ( conversionApi.consumable.consume( modelChildNode, 'insert' ) ) { - conversionApi.writer.move( - conversionApi.writer.createRangeOn( viewChildNode ), - conversionApi.mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); - } + consumable.consume( modelChildNode, 'insert' ); + + writer.move( + writer.createRangeOn( viewChildNode ), + mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) + ); } else { // Note that this is not creating another consumable, it's using the current one. conversionApi.convert( ModelRange._createOn( modelChildNode ) ); diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 4cf16b5eac5..b8b54e2ce75 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -1403,6 +1403,54 @@ describe( 'DowncastHelpers', () => { } ); } ); + it( 'should consume reinserted elements', () => { + model.schema.register( 'simple', { + allowIn: '$root' + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'simple', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const element = writer.createContainerElement( 'div' ); + + writer.insert( writer.createPositionAt( element, 'end' ), slotFor( 'children' ) ); + + return element; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simple' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + setModelData( model, 'foo' ); + + let consumable; + + controller.downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + } ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + + writer.insertText( 'bar', paragraph, 0 ); + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + } ); + + expect( consumable.test( modelRoot.getNodeByPath( [ 0, 0 ] ), 'insert' ) ).to.be.false; + expect( consumable.test( modelRoot.getNodeByPath( [ 0, 1 ] ), 'insert' ) ).to.be.false; + } ); + it( 'should throw an exception if invalid slot mode or filter was provided', () => { model.schema.register( 'simple', { allowIn: '$root' @@ -1440,36 +1488,102 @@ describe( 'DowncastHelpers', () => { writer.insert( paragraph, simple, 0 ); writer.insert( simple, modelRoot, 0 ); } ); - }, /^conversion-slot-mode-unknown/, null, { modeOrFilter: 'foo' } ); + }, /^conversion-slot-mode-unknown/, controller.downcastDispatcher, { modeOrFilter: 'foo' } ); + } ); + + it( 'should throw an exception if slot filter results overlap', () => { + model.schema.register( 'complex', { + allowIn: '$root' + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const outer = writer.createContainerElement( 'div' ); + const inner1 = writer.createContainerElement( 'div', { class: 'inner-first' } ); + const inner2 = writer.createContainerElement( 'div', { class: 'inner-second' } ); - // TODO add context instead of null + writer.insert( writer.createPositionAt( outer, 'end' ), inner1 ); + writer.insert( writer.createPositionAt( outer, 'end' ), inner2 ); + + writer.insert( writer.createPositionAt( inner1, 0 ), slotFor( element => element.index <= 1 ) ); + writer.insert( writer.createPositionAt( inner2, 0 ), slotFor( element => element.index >= 1 ) ); + + return outer; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'complex' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + expectToThrowCKEditorError( () => { + model.change( writer => { + const complex = writer.createElement( 'complex' ); + + writer.insertElement( 'paragraph', complex, 0 ); + writer.insertElement( 'paragraph', complex, 0 ); + writer.insertElement( 'paragraph', complex, 0 ); + writer.insert( complex, modelRoot, 0 ); + } ); + }, /^conversion-slot-filter-to-permissive/, controller.downcastDispatcher ); } ); - // it( 'should throw an exception if invalid slot mode or filrer was provided', () => { - // model.schema.register( 'complex', { - // allowIn: '$root' - // } ); - // - // downcastHelpers.elementToStructure( { - // model: { - // name: 'complex', - // children: true - // }, - // view: ( modelElement, { writer, slotFor } ) => { - // const outer = writer.createContainerElement( 'div' ); - // const inner1 = writer.createContainerElement( 'div', { class: 'inner-first' } ); - // const inner2 = writer.createContainerElement( 'div', { class: 'inner-second' } ); - // - // writer.insert( writer.createPositionAt( outer, 'end' ), inner1 ); - // writer.insert( writer.createPositionAt( outer, 'end' ), inner2 ); - // - // writer.insert( writer.createPositionAt( inner1, 0 ), slotFor( element => element.index < 2 ) ); - // writer.insert( writer.createPositionAt( inner2, 0 ), slotFor( element => element.index >= 2 ) ); - // - // return outer; - // } - // } ); - // } ); + it( 'should throw an exception if slot filter not include all children', () => { + model.schema.register( 'complex', { + allowIn: '$root' + } ); + + downcastHelpers.elementToStructure( { + model: { + name: 'complex', + children: true + }, + view: ( modelElement, { writer, slotFor } ) => { + const outer = writer.createContainerElement( 'div' ); + const inner1 = writer.createContainerElement( 'div', { class: 'inner-first' } ); + const inner2 = writer.createContainerElement( 'div', { class: 'inner-second' } ); + + writer.insert( writer.createPositionAt( outer, 'end' ), inner1 ); + writer.insert( writer.createPositionAt( outer, 'end' ), inner2 ); + + writer.insert( writer.createPositionAt( inner1, 0 ), slotFor( element => element.index < 1 ) ); + writer.insert( writer.createPositionAt( inner2, 0 ), slotFor( element => element.index > 1 ) ); + + return outer; + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'complex' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + expectToThrowCKEditorError( () => { + model.change( writer => { + const complex = writer.createElement( 'complex' ); + + writer.insertElement( 'paragraph', complex, 0 ); + writer.insertElement( 'paragraph', complex, 0 ); + writer.insertElement( 'paragraph', complex, 0 ); + writer.insert( complex, modelRoot, 0 ); + } ); + }, /^conversion-slot-filter-to-restrictive/, controller.downcastDispatcher ); + } ); } ); describe( 'attributeToElement()', () => { @@ -2014,7 +2128,7 @@ describe( 'DowncastHelpers', () => { writer.insertText( 'Foo', { test: '2' }, modelRoot.getChild( 0 ), 0 ); writer.insertText( 'Bar', { test: '3' }, modelRoot.getChild( 1 ), 0 ); } ); - }, /^conversion-attribute-to-attribute-on-text/ ); + }, /^conversion-attribute-to-attribute-on-text/, controller.downcastDispatcher ); } ); it( 'should convert attribute insert/change/remove on a model node', () => { From 0053bd0fd1c612be5bb01fa834270333f33004e0 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 6 Sep 2021 13:59:35 +0200 Subject: [PATCH 022/140] Reverted changes. --- .../tests/manual/slotconversion.html | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index aa666f43305..7ecc38a4691 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -36,29 +36,9 @@ padding: 0 2px; opacity: 0.8; } - .ck.ck-content div[data-slot] { - padding: 15px; - border: 1px dotted hsl(285, 100%, 50%); - background: hsla(285, 100%, 50%, .3); - position: relative; - } - .ck.ck-content div[data-slot]:after { - content: attr(data-slot); - position: absolute; - font-size: 10px; - top: -1px; - right: -1px; - color: hsl(0, 0%, 100%); - border: 1px solid hsl(285, 100%, 50%); - background: hsla(285, 100%, 50%, .6); - padding: 1px 5px; - } -

elementToStructure

-

Separate converters for box (with slots) and boxField

-
From 50bc5e56dff497c1d976774c68e17f679920f70e Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 6 Sep 2021 17:50:51 +0200 Subject: [PATCH 023/140] Tuned slot conversion manual test. --- .../tests/manual/slotconversion.html | 11 ++ .../tests/manual/slotconversion.js | 111 ++++++++++-------- .../tests/manual/slotconversion.md | 10 +- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slotconversion.html index 7ecc38a4691..fd504d810fd 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.html +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.html @@ -16,6 +16,12 @@ background: hsl(0, 0%, 60%); } + .box-footer { + border: 1px solid hsl(0, 0%, 80%); + background: hsl(0, 0%, 40%); + color: hsl(0, 0%, 80%); + } + .box-content-field { padding: .5em; background: hsl(0, 0%, 100%); @@ -39,6 +45,11 @@ +
+ + +
+
diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slotconversion.js index 2512bd42dc7..e7749b0667f 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.js +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* globals console, window, document */ +/* globals window, document */ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; @@ -41,7 +41,7 @@ function mapMeta( editor ) { } function getChildren( editor, viewElement ) { - return [ ...( editor.editing.view.createRangeIn( viewElement ) ) ] + return Array.from( editor.editing.view.createRangeIn( viewElement ) ) .filter( ( { type } ) => type === 'elementStart' ) .map( ( { item } ) => item ); } @@ -73,62 +73,67 @@ function getBoxUpcastConverter( editor ) { for ( const field of fields ) { const boxField = writer.createElement( 'boxField' ); - conversionApi.safeInsert( boxField, writer.createPositionAt( box, field.index ) ); + conversionApi.safeInsert( boxField, writer.createPositionAt( box, 'end' ) ); conversionApi.convertChildren( field, boxField ); } conversionApi.consumable.consume( viewElement, { name: true } ); - elements.map( element => { - conversionApi.consumable.consume( element, { name: true } ); - } ); + elements.forEach( element => conversionApi.consumable.consume( element, { name: true } ) ); conversionApi.updateConversionResult( box, data ); } ); } -function downcastBox( modelElement, conversionApi ) { - const { writer, slotFor } = conversionApi; +function getBoxDowncastCreator( multiSlot ) { + return ( modelElement, conversionApi ) => { + const { writer, slotFor } = conversionApi; - const viewBox = writer.createContainerElement( 'div', { class: 'box' } ); - const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } ); + const viewBox = writer.createContainerElement( 'div', { class: 'box' } ); + const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } ); - writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap ); + writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap ); - for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) { - if ( meta === 'header' ) { - const header = writer.createRawElement( 'div', { - class: 'box-meta box-meta-header' - }, domElement => { - domElement.innerHTML = `

${ metaValue.title }

`; - } ); + for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) { + if ( meta === 'header' ) { + const header = writer.createRawElement( 'div', { + class: 'box-meta box-meta-header' + }, domElement => { + domElement.innerHTML = `

${ metaValue.title }

`; + } ); - writer.insert( writer.createPositionBefore( contentWrap ), header ); - } + writer.insert( writer.createPositionBefore( contentWrap ), header ); + } - if ( meta === 'author' ) { - const author = writer.createRawElement( 'div', { - class: 'box-meta box-meta-author' - }, domElement => { - domElement.innerHTML = `${ metaValue.name }`; - } ); + if ( meta === 'author' ) { + const author = writer.createRawElement( 'div', { + class: 'box-meta box-meta-author' + }, domElement => { + domElement.innerHTML = `${ metaValue.name }`; + } ); - writer.insert( writer.createPositionAfter( contentWrap ), author ); + writer.insert( writer.createPositionAfter( contentWrap ), author ); + } } - } - // switch for testing filtered vs all children - if ( 0 ) { - writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( 'children' ) ); - } else { - writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( element => element.index < 2 ) ); + if ( !multiSlot ) { + writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( 'children' ) ); + } else { + writer.insert( writer.createPositionAt( contentWrap, 0 ), slotFor( element => element.index < 2 ) ); - const contentWrap2 = writer.createContainerElement( 'div', { class: 'box-content' } ); + const contentWrap2 = writer.createContainerElement( 'div', { class: 'box-content' } ); - writer.insert( writer.createPositionAt( viewBox, 'end' ), contentWrap2 ); - writer.insert( writer.createPositionAt( contentWrap2, 0 ), slotFor( element => element.index >= 2 ) ); - } + writer.insert( writer.createPositionAt( viewBox, 'end' ), contentWrap2 ); + writer.insert( writer.createPositionAt( contentWrap2, 0 ), slotFor( element => element.index >= 2 ) ); + + const footer = writer.createRawElement( 'div', { class: 'box-footer' }, domElement => { + domElement.innerHTML = 'Footer'; + } ); + + writer.insert( writer.createPositionAfter( contentWrap2 ), footer ); + } - return viewBox; + return viewBox; + }; } function addButton( editor, uiName, label, callback ) { @@ -183,7 +188,7 @@ function Box( editor ) { attributes: [ 'meta' ], children: true }, - view: downcastBox + view: getBoxDowncastCreator( editor.config.get( 'box.multiSlot' ) ) } ); editor.conversion.for( 'downcast' ).elementToElement( { @@ -230,8 +235,8 @@ function AddRenderCount( editor ) { }, { priority: 'lowest' } ) ); } -ClassicEditor - .create( document.querySelector( '#editor' ), { +async function createEditor( multiSlot ) { + const editor = await ClassicEditor.create( document.querySelector( '#editor' ), { plugins: [ ArticlePluginSet, Box, AddRenderCount ], toolbar: [ 'heading', @@ -265,11 +270,23 @@ ClassicEditor 'tableRow', 'mergeTableCells' ] + }, + box: { multiSlot } + } ); + + window.editor = editor; +} + +for ( const option of document.querySelectorAll( 'input[name=box-mode]' ) ) { + option.addEventListener( 'change', async event => { + if ( window.editor ) { + await window.editor.destroy(); } - } ) - .then( editor => { - window.editor = editor; - } ) - .catch( err => { - console.error( err.stack ); + + await createEditor( event.target.value == 'multi' ); } ); + + if ( option.checked ) { + createEditor( option.value == 'multi' ); + } +} diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.md b/packages/ckeditor5-engine/tests/manual/slotconversion.md index bab4d7202b1..a8ac8f99231 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.md +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.md @@ -1,12 +1,14 @@ # Slot conversion -The editor should be loaded with a "box" element that contains multiple "slots" in which user can edit content. +The editor should be loaded with a "box" element that contains multiple fields in which user can edit content. -An additional converter adds `"data-insert-count"` attribute to view elements to show when it was rendered. It is displayed with a CSS at the top-right corner of rendered element. If a view element was not re-rendered this attribute should not change. *Note*: it only acts on "insert" changes so it can omit attribute-to-element changes or insertions not passed through dispatcher. +An additional converter adds `"data-insert-count"` attribute to view elements to show when it was rendered. It is displayed with a CSS in the top-right corner of rendered element. If a view element was not re-rendered this attribute should not change. *Note*: it only acts on "insert" changes, so it can omit attribute-to-element changes or insertions not passed through dispatcher. Observe which view elements are re-rendered when using UI-buttons: * `Box title` - updates title attribute which triggers re-rendering of a "box". * `Box author` - updates author attribute which triggers re-rendering of a "box". -* `+` - adds "slot" to box" which triggers re-rendering of a "box". -* `-` - removes "slot" from box" which triggers re-rendering of a "box". +* `+` - adds field to box which triggers re-rendering of a "box". +* `-` - removes field from box which triggers re-rendering of a "box". + +There is a switch above the editor to load single slot version of the plugin (where all fields are in a single wrapper), and a multi-slot version (where first 2 fields are in one wrapper and the rest in the other wrapper). From edcdaa849eac906249bf0bc0bc5ef9b07c44b76f Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 6 Sep 2021 18:13:39 +0200 Subject: [PATCH 024/140] Fixed tests. --- .../ckeditor5-find-and-replace/tests/findcommand.js | 10 +++++++--- .../ckeditor5-find-and-replace/tests/replacecommand.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-find-and-replace/tests/findcommand.js b/packages/ckeditor5-find-and-replace/tests/findcommand.js index 0f8c03e46e8..4af496cd382 100644 --- a/packages/ckeditor5-find-and-replace/tests/findcommand.js +++ b/packages/ckeditor5-find-and-replace/tests/findcommand.js @@ -61,7 +61,7 @@ describe( 'FindCommand', () => { const markers = getSimplifiedMarkersFromResults( results ); expect( stringify( model.document.getRoot(), null, markers ) ).to.equal( - 'Foo bar baz. Bam bar bom.' + 'Foo bar baz. Bam bar bom.' ); } ); @@ -325,7 +325,7 @@ describe( 'FindCommand', () => { ); expect( stringify( multiRootModel.document.getRoot( 'second' ), null, [ markerSecond ] ) ).to.equal( - 'Foo bar baz' + 'Foo bar baz' ); } ); @@ -358,9 +358,13 @@ describe( 'FindCommand', () => { * random and unique. */ function getSimplifiedMarkersFromResults( results ) { + let letter = 'X'; + return results.map( item => { // Replace markers id to a predefined value, as originally these are unique random ids. - item.marker.name = 'X'; + item.marker.name = letter; + + letter = String.fromCharCode( letter.charCodeAt( 0 ) + 1 ); return item.marker; } ); diff --git a/packages/ckeditor5-find-and-replace/tests/replacecommand.js b/packages/ckeditor5-find-and-replace/tests/replacecommand.js index cee7273f08a..2db579276fd 100644 --- a/packages/ckeditor5-find-and-replace/tests/replacecommand.js +++ b/packages/ckeditor5-find-and-replace/tests/replacecommand.js @@ -95,7 +95,7 @@ describe( 'ReplaceCommand', () => { } } - expect( getData( editor.model, { convertMarkers: true } ) ).to.equal( + expect( getData( editor.model, { convertMarkers: true, withoutSelection: true } ) ).to.equal( 'bar ' + 'foo' + ' ' + From 82f01764669d8334d6b122f78013d83a6654d552 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 7 Sep 2021 18:37:34 +0200 Subject: [PATCH 025/140] Code cleanup. --- .../src/conversion/downcastdispatcher.js | 17 ++++++++------- .../src/conversion/modelconsumable.js | 21 ++++++++++++------- .../tests/conversion/modelconsumable.js | 17 +++++++++++++++ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index c0c95a6799f..1d3b6a032ed 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -267,7 +267,7 @@ export default class DowncastDispatcher { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertInsert( range, conversionApi ) { - // Create a list of things that can be consumed, consisting of nodes and their attributes. + // Collect a list of things that can be consumed, consisting of nodes and their attributes. this._addConsumablesForInsert( conversionApi.consumable, range ); // Fire a separate insert event for each node and text fragment contained in the range. @@ -335,13 +335,14 @@ export default class DowncastDispatcher { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertReinsert( range, conversionApi ) { - // Create a list of things that can be consumed, consisting of nodes and their attributes. + // Collect a list of things that can be consumed, consisting of nodes and their attributes. this._addConsumablesForInsert( conversionApi.consumable, range ); // Convert the elements - without converting children. - // - // Fire a separate insert event for each node and text fragment contained in the range. - for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { + const walkerValues = Array.from( range.getWalker( { shallow: true, ignoreElementEnd: true } ) ); + + // Fire a separate insert event for each node and text fragment contained shallowly in the range. + for ( const data of walkerValues.map( walkerValueToEventData ) ) { this._convertInsertWithAttributes( { ...data, reconversion: true }, conversionApi ); } } @@ -436,7 +437,7 @@ export default class DowncastDispatcher { } /** - * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume from a given range, + * Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume from a given range, * assuming that the range has just been inserted to the model. * * @private @@ -462,7 +463,7 @@ export default class DowncastDispatcher { } /** - * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume for a given range. + * Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume for a given range. * * @private * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. @@ -479,7 +480,7 @@ export default class DowncastDispatcher { } /** - * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with selection consumable values. + * Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with selection consumable values. * * @private * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index 6a35c0a4f64..20b0175b694 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -270,24 +270,26 @@ export default class ModelConsumable { } if ( !symbol ) { - symbol = this._addSymbolForTextProxy( textProxy.startOffset, textProxy.endOffset, textProxy.parent ); + symbol = this._addSymbolForTextProxy( textProxy ); } return symbol; } /** - * Adds a symbol for given properties that characterizes a {@link module:engine/model/textproxy~TextProxy} instance. + * Adds a symbol for given {@link module:engine/model/textproxy~TextProxy} instance. * * Used internally to correctly consume `TextProxy` instances. * * @private - * @param {Number} startIndex Text proxy start index in it's parent. - * @param {Number} endIndex Text proxy end index in it's parent. - * @param {module:engine/model/element~Element} parent Text proxy parent. - * @returns {Symbol} Symbol generated for given properties. + * @param {module:engine/model/textproxy~TextProxy} textProxy Text proxy instance. + * @returns {Symbol} Symbol generated for given `TextProxy`. */ - _addSymbolForTextProxy( start, end, parent ) { + _addSymbolForTextProxy( textProxy ) { + const start = textProxy.startOffset; + const end = textProxy.endOffset; + const parent = textProxy.parent; + const symbol = Symbol( 'textProxySymbol' ); let startMap, endMap; @@ -320,6 +322,11 @@ export default class ModelConsumable { function _normalizeConsumableType( type ) { const parts = type.split( ':' ); + // For inserts allow passing event name, it's stored in the context of a specified element so the element name is not needed. + if ( parts[ 0 ] == 'insert' ) { + return parts[ 0 ]; + } + // Markers are identified by the whole name (otherwise we would consume the whole markers group). if ( parts[ 0 ] == 'addMarker' || parts[ 0 ] == 'removeMarker' ) { return type; diff --git a/packages/ckeditor5-engine/tests/conversion/modelconsumable.js b/packages/ckeditor5-engine/tests/conversion/modelconsumable.js index 6ec4aeafa60..31974e927cb 100644 --- a/packages/ckeditor5-engine/tests/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/tests/conversion/modelconsumable.js @@ -52,6 +52,13 @@ describe( 'ModelConsumable', () => { expect( modelConsumable.test( modelElement, 'foo:xxx' ) ).to.be.null; } ); + it( 'should normalize type name for inserts', () => { + modelConsumable.add( modelElement, 'insert:foo' ); + + expect( modelConsumable.test( modelElement, 'insert:foo' ) ).to.be.true; + expect( modelConsumable.test( modelElement, 'insert' ) ).to.be.true; + } ); + it( 'should not normalize type name for markers', () => { modelConsumable.add( modelElement, 'addMarker:foo:bar:baz:abc' ); modelConsumable.add( modelElement, 'removeMarker:foo:bar:baz:abc' ); @@ -114,6 +121,16 @@ describe( 'ModelConsumable', () => { expect( modelConsumable.test( modelElement, 'foo:bar' ) ).to.be.false; } ); + it( 'should normalize type name for inserts', () => { + modelConsumable.add( modelElement, 'insert' ); + const result = modelConsumable.consume( modelElement, 'insert:foo' ); + + expect( result ).to.be.true; + + expect( modelConsumable.test( modelElement, 'insert:foo' ) ).to.be.false; + expect( modelConsumable.test( modelElement, 'insert' ) ).to.be.false; + } ); + it( 'should not normalize type names for markers', () => { modelConsumable.add( modelElement, 'addMarker:foo:bar:baz' ); modelConsumable.add( modelElement, 'removeMarker:foo:bar:baz' ); From 818a93a231e56b54ddff8515bbb16223b9172e56 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 7 Sep 2021 18:52:30 +0200 Subject: [PATCH 026/140] Code cleanup. --- .../ckeditor5-engine/src/conversion/downcastdispatcher.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 1d3b6a032ed..2bbc2d7b120 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -339,7 +339,7 @@ export default class DowncastDispatcher { this._addConsumablesForInsert( conversionApi.consumable, range ); // Convert the elements - without converting children. - const walkerValues = Array.from( range.getWalker( { shallow: true, ignoreElementEnd: true } ) ); + const walkerValues = Array.from( range.getWalker( { shallow: true } ) ); // Fire a separate insert event for each node and text fragment contained shallowly in the range. for ( const data of walkerValues.map( walkerValueToEventData ) ) { @@ -445,8 +445,8 @@ export default class DowncastDispatcher { * @param {module:engine/model/range~Range} range The inserted range. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ - _addConsumablesForInsert( consumable, range ) { - for ( const value of range ) { + _addConsumablesForInsert( consumable, walkerValues ) { + for ( const value of walkerValues ) { const item = value.item; // Add consumable if it wasn't there yet. From 1f2df2b805c35de6145d5382177085a905f2ca24 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 8 Sep 2021 13:36:52 +0200 Subject: [PATCH 027/140] Only items that need conversion should be added to ModelConsumable. --- .../src/conversion/downcastdispatcher.js | 16 ++++--- .../src/conversion/downcasthelpers.js | 7 +-- .../tests/conversion/downcastdispatcher.js | 2 +- .../tests/conversion/downcasthelpers.js | 48 ------------------- 4 files changed, 12 insertions(+), 61 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 2bbc2d7b120..723ab8e7b6c 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -267,11 +267,13 @@ export default class DowncastDispatcher { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertInsert( range, conversionApi ) { + const walkerValues = Array.from( range ); + // Collect a list of things that can be consumed, consisting of nodes and their attributes. - this._addConsumablesForInsert( conversionApi.consumable, range ); + this._addConsumablesForInsert( conversionApi.consumable, walkerValues ); // Fire a separate insert event for each node and text fragment contained in the range. - for ( const data of Array.from( range ).map( walkerValueToEventData ) ) { + for ( const data of walkerValues.map( walkerValueToEventData ) ) { this._convertInsertWithAttributes( data, conversionApi ); } } @@ -335,12 +337,12 @@ export default class DowncastDispatcher { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _convertReinsert( range, conversionApi ) { - // Collect a list of things that can be consumed, consisting of nodes and their attributes. - this._addConsumablesForInsert( conversionApi.consumable, range ); - // Convert the elements - without converting children. const walkerValues = Array.from( range.getWalker( { shallow: true } ) ); + // Collect a list of things that can be consumed, consisting of nodes and their attributes. + this._addConsumablesForInsert( conversionApi.consumable, walkerValues ); + // Fire a separate insert event for each node and text fragment contained shallowly in the range. for ( const data of walkerValues.map( walkerValueToEventData ) ) { this._convertInsertWithAttributes( { ...data, reconversion: true }, conversionApi ); @@ -442,7 +444,7 @@ export default class DowncastDispatcher { * * @private * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable. - * @param {module:engine/model/range~Range} range The inserted range. + * @param {Iterable.} walkerValues The walker values for the inserted range. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume. */ _addConsumablesForInsert( consumable, walkerValues ) { @@ -536,7 +538,7 @@ export default class DowncastDispatcher { consumable: new Consumable(), writer, options, - convert: range => this._convertInsert( range, conversionApi ) + convertInsert: range => this._convertInsert( range, conversionApi ) }; return conversionApi; diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 5f6e4f5ce83..3620a00b332 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2060,22 +2060,19 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { // @param {Object} options // @param {Boolean} [options.reconversion] function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { - const { writer, mapper, consumable } = conversionApi; + const { writer, mapper } = conversionApi; // Fill with nested view nodes. for ( const modelChildNode of modelNodes ) { const viewChildNode = mapper.toViewElement( modelChildNode ); if ( options.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { - consumable.consume( modelChildNode, 'insert' ); - writer.move( writer.createRangeOn( viewChildNode ), mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) ); } else { - // Note that this is not creating another consumable, it's using the current one. - conversionApi.convert( ModelRange._createOn( modelChildNode ) ); + conversionApi.convertInsert( ModelRange._createOn( modelChildNode ) ); } } } diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index af525144060..34e88de79f6 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -571,7 +571,7 @@ describe( 'DowncastDispatcher', () => { dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { if ( conversionApi.consumable.consume( data.item, 'insert' ) ) { - conversionApi.convert( data.range ); + conversionApi.convertInsert( data.range ); } } ); diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index b8b54e2ce75..665f69d0137 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -1403,54 +1403,6 @@ describe( 'DowncastHelpers', () => { } ); } ); - it( 'should consume reinserted elements', () => { - model.schema.register( 'simple', { - allowIn: '$root' - } ); - - downcastHelpers.elementToStructure( { - model: { - name: 'simple', - children: true - }, - view: ( modelElement, { writer, slotFor } ) => { - const element = writer.createContainerElement( 'div' ); - - writer.insert( writer.createPositionAt( element, 'end' ), slotFor( 'children' ) ); - - return element; - } - } ); - - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'simple' - } ); - - downcastHelpers.elementToElement( { - model: 'paragraph', - view: 'p' - } ); - - setModelData( model, 'foo' ); - - let consumable; - - controller.downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => { - consumable = conversionApi.consumable; - } ); - - model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - - writer.insertText( 'bar', paragraph, 0 ); - writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); - } ); - - expect( consumable.test( modelRoot.getNodeByPath( [ 0, 0 ] ), 'insert' ) ).to.be.false; - expect( consumable.test( modelRoot.getNodeByPath( [ 0, 1 ] ), 'insert' ) ).to.be.false; - } ); - it( 'should throw an exception if invalid slot mode or filter was provided', () => { model.schema.register( 'simple', { allowIn: '$root' From 9563fb2d9e3608dd09d66fed99d68f4d6220829d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Wed, 8 Sep 2021 14:36:35 +0200 Subject: [PATCH 028/140] Fix the schema definition. --- packages/ckeditor5-engine/tests/manual/slotconversion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slotconversion.js index e7749b0667f..cb40144f5ec 100644 --- a/packages/ckeditor5-engine/tests/manual/slotconversion.js +++ b/packages/ckeditor5-engine/tests/manual/slotconversion.js @@ -171,7 +171,7 @@ function Box( editor ) { allowIn: '$root', isObject: true, isSelectable: true, - allowAttributes: [ 'infoBoxMeta' ] + allowAttributes: [ 'meta' ] } ); editor.model.schema.register( 'boxField', { From f72e9006d3ecf38e7e6e332298f8e44aab3f6931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Wed, 8 Sep 2021 14:57:43 +0200 Subject: [PATCH 029/140] Improved errors documentation. --- .../src/conversion/downcasthelpers.js | 17 +++++++++++------ .../tests/conversion/downcasthelpers.js | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 3620a00b332..2d0ff907b54 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1993,20 +1993,25 @@ function validateSlotsChildren( element, slotsMap, conversionApi ) { if ( uniqueChildrenInSlots.size != childrenInSlots.length ) { /** - * A filter provided to `conversionApi.slotFor()` is to permissive and leads to downcasting a same node to a multiple slots. + * Filters provided to `conversionApi.slotFor()` overlap (at least two filters accept the same child element). * - * @error conversion-slot-filter-to-permissive + * @error conversion-slot-filter-overlap + * @param {module:engine/model/element~Element} element The element of which children would not be properly + * allocated to multiple slots. */ - throw new CKEditorError( 'conversion-slot-filter-to-permissive', conversionApi.dispatcher, { element, slotsMap } ); + throw new CKEditorError( 'conversion-slot-filter-overlap', conversionApi.dispatcher, { element } ); } if ( uniqueChildrenInSlots.size != element.childCount ) { /** - * A filter provided to `conversionApi.slotFor()` is to restrictive and leads to missing some nodes while downcasting. + * Filters provided to `conversionApi.slotFor()` are incomplete and exclude at least one children element (one of + * the children elements would not be assigned to any of the slots). * - * @error conversion-slot-filter-to-restrictive + * @error conversion-slot-filter-incomplete + * @param {module:engine/model/element~Element} element The element of which children would not be properly + * allocated to multiple slots. */ - throw new CKEditorError( 'conversion-slot-filter-to-restrictive', conversionApi.dispatcher, { element, slotsMap } ); + throw new CKEditorError( 'conversion-slot-filter-incomplete', conversionApi.dispatcher, { element } ); } } diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 665f69d0137..297dd40d4de 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -1487,7 +1487,7 @@ describe( 'DowncastHelpers', () => { writer.insertElement( 'paragraph', complex, 0 ); writer.insert( complex, modelRoot, 0 ); } ); - }, /^conversion-slot-filter-to-permissive/, controller.downcastDispatcher ); + }, /^conversion-slot-filter-overlap/, controller.downcastDispatcher ); } ); it( 'should throw an exception if slot filter not include all children', () => { @@ -1534,7 +1534,7 @@ describe( 'DowncastHelpers', () => { writer.insertElement( 'paragraph', complex, 0 ); writer.insert( complex, modelRoot, 0 ); } ); - }, /^conversion-slot-filter-to-restrictive/, controller.downcastDispatcher ); + }, /^conversion-slot-filter-incomplete/, controller.downcastDispatcher ); } ); } ); From ab03609f2c599ed4aa9ee758d1e14e8cdc9088cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Wed, 8 Sep 2021 15:48:20 +0200 Subject: [PATCH 030/140] Wording. --- .../src/controller/datacontroller.js | 1 - .../src/conversion/downcastdispatcher.js | 41 ++++++++++++------- .../ckeditor5-engine/src/conversion/mapper.js | 12 +++--- .../tests/conversion/downcastdispatcher.js | 32 +++++++-------- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js index cf17c3302c6..d63d71af341 100644 --- a/packages/ckeditor5-engine/src/controller/datacontroller.js +++ b/packages/ckeditor5-engine/src/controller/datacontroller.js @@ -250,7 +250,6 @@ export default class DataController { modelElementOrFragment.markers : _getMarkersRelativeToElement( modelElementOrFragment ); - // We have no view controller and rendering to DOM in DataController so view.change() block is not used here. this.downcastDispatcher.convert( modelRange, markers, viewWriter, options ); return viewDocumentFragment; diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 723ab8e7b6c..b3aee26998c 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -127,14 +127,21 @@ export default class DowncastDispatcher { } /** - * Takes a {@link module:engine/model/differ~Differ model differ} object with buffered changes and fires conversion basing on it. + * Converts changes buffered in the given {@link module:engine/model/differ~Differ model differ} + * and fires conversion events based on it. * + * @fires insert + * @fires remove + * @fires attribute + * @fires addMarker + * @fires removeMarker + * @fires reducedChanges * @param {module:engine/model/differ~Differ} differ The differ object with buffered changes. - * @param {module:engine/model/markercollection~MarkerCollection} markers Markers connected with the converted model. + * @param {module:engine/model/markercollection~MarkerCollection} markers Markers related to the model fragment to convert. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ convertChanges( differ, markers, writer ) { - const conversionApi = this._prepareConversionApi( writer ); + const conversionApi = this._createConversionApi( writer ); // Before the view is updated, remove markers which have changed. for ( const change of differ.getMarkersToRemove() ) { @@ -186,7 +193,7 @@ export default class DowncastDispatcher { * @param {Object} [options] Optional options object passed to `convertionApi.options`. */ convert( range, markers, writer, options = {} ) { - const conversionApi = this._prepareConversionApi( writer, options ); + const conversionApi = this._createConversionApi( writer, options ); this._convertInsert( range, conversionApi ); @@ -210,7 +217,7 @@ export default class DowncastDispatcher { convertSelection( selection, markers, writer ) { const markersAtSelection = Array.from( markers.getMarkersAtPosition( selection.getFirstPosition() ) ); - const conversionApi = this._prepareConversionApi( writer ); + const conversionApi = this._createConversionApi( writer ); this._addConsumablesForSelection( conversionApi.consumable, selection, markersAtSelection ); @@ -255,7 +262,7 @@ export default class DowncastDispatcher { } /** - * Fires insertion conversion of a range of elements. + * Fires insertion conversion of a range of nodes. * * For each node in the range, {@link #event:insert `insert` event is fired}. For each attribute on each node, * {@link #event:attribute `attribute` event is fired}. @@ -325,9 +332,10 @@ export default class DowncastDispatcher { } /** - * Fires re-insertion conversion of a range of elements (only elements on the range depth, without children). + * Fires re-insertion conversion (with a `reconversion` flag passed to `insert` events) + * of a range of elements (only elements on the range depth, without children). * - * For each node in the range on it's depth (without children), {@link #event:insert `insert` event} is fired. + * For each node in the range on its depth (without children), {@link #event:insert `insert` event} is fired. * For each attribute on each node, {@link #event:attribute `attribute` event} is fired. * * @protected @@ -532,7 +540,7 @@ export default class DowncastDispatcher { * @param {Object} [options] Optional options passed to `convertionApi.options`. * @return {module:engine/conversion/downcastdispatcher~DowncastConversionApi} The conversion API object. */ - _prepareConversionApi( writer, options = {} ) { + _createConversionApi( writer, options = {} ) { const conversionApi = { ...this._conversionApi, consumable: new Consumable(), @@ -569,12 +577,14 @@ export default class DowncastDispatcher { } /** - * Fired for the reduction of changes buffered in the {@link module:engine/model/differ~Differ `Differ`} before - * {@link #convertChanges `convertChanges()`} will fire any events to convert changes. + * Fired to enable reducing (transforming) changes buffered in the {@link module:engine/model/differ~Differ `Differ`} before + * {@link #convertChanges `convertChanges()`} will fire any conversion events. * - * Features can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with `reinsert` entries to trigger - * reconversion. The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure - * `DowncastHelpers.elementToStructure()`} is using this event to trigger reconversion. + * For instance, a feature can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with a `reinsert` entry + * to trigger reconversion of an element when e.g. its attribute has changes. + * The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure + * `DowncastHelpers.elementToStructure()`} helper is using this event to trigger reconversion of an element when the element, + * its attributes or direct children changed. * * @param {Object} data * @param {Iterable.} data.changes A buffered changes to get reduced. @@ -784,7 +794,8 @@ function walkerValueToEventData( value ) { */ /** - * Triggers the nested insert conversion on a specified range. + * Triggers conversion of a specified range. + * This conversion is triggered within (as a separate process of) the parent conversion. * * @method #convertInsert * @param {module:engine/model/range~Range} range The range to trigger nested insert conversion on. diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index fb6b6c0169b..04c304b1ab5 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -89,7 +89,7 @@ export default class Mapper { this._elementToMarkerNames = new Map(); /** - * Map of removed view elements with their current root (used for deferred unbinding). + * The map of removed view elements with their current root (used for deferred unbinding). * * @private * @member {Map.} @@ -145,17 +145,17 @@ export default class Mapper { } /** - * Unbinds given {@link module:engine/view/element~Element view element} from the map. + * Unbinds the given {@link module:engine/view/element~Element view element} from the map. * * **Note:** view-to-model binding will be removed, if it existed. However, corresponding model-to-view binding - * will be removed only if model element is still bound to passed `viewElement`. + * will be removed only if model element is still bound to the passed `viewElement`. * * This behavior lets for re-binding model element to another view element without fear of losing the new binding * when the previously bound view element is unbound. * * @param {module:engine/view/element~Element} viewElement View element to unbind. * @param {Object} [options={}] The options object. - * @param {Boolean} [options.defer=false] Controls whether binding should be immediately removed or deferred until the + * @param {Boolean} [options.defer=false] Controls whether the binding should be removed immediately or deferred until a * {@link #flushDeferredBindings `flushDeferredBindings()`} call. */ unbindViewElement( viewElement, options = {} ) { @@ -260,7 +260,9 @@ export default class Mapper { } /** - * Unbinds all deferred binding removals that were not re-attached to some root or document fragment. + * Unbinds all deferred binding removals of view elements that were not re-attached in the meantime to some root or document fragment. + * + * See: {@link #unbindViewElement `unbindViewElement()`}. */ flushDeferredBindings() { for ( const [ viewElement, root ] of this._deferredBindingRemovals ) { diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 34e88de79f6..f8b799e3b91 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -492,7 +492,7 @@ describe( 'DowncastDispatcher', () => { } ); view.change( writer => { - dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); } ); // Check the data passed to called events and the order of them. @@ -529,7 +529,7 @@ describe( 'DowncastDispatcher', () => { const range = model.createRangeIn( root ); view.change( writer => { - dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); } ); expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; @@ -586,7 +586,7 @@ describe( 'DowncastDispatcher', () => { const range = model.createRangeIn( root ); view.change( writer => { - dispatcher._convertInsert( range, dispatcher._prepareConversionApi( writer ) ); + dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); } ); expect( loggedEvents ).to.deep.equal( [ @@ -639,7 +639,7 @@ describe( 'DowncastDispatcher', () => { } ); view.change( writer => { - dispatcher._convertReinsert( range, dispatcher._prepareConversionApi( writer ) ); + dispatcher._convertReinsert( range, dispatcher._createConversionApi( writer ) ); } ); // Check the data passed to called events and the order of them. @@ -950,7 +950,7 @@ describe( 'DowncastDispatcher', () => { expect( data.markerRange.isEqual( range ) ).to.be.true; } ); - dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._createConversionApi() ); expect( spy.calledOnce ).to.be.true; @@ -964,7 +964,7 @@ describe( 'DowncastDispatcher', () => { const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher._convertMarkerAdd( 'name', eleRange, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'name', eleRange, dispatcher._createConversionApi() ); expect( dispatcher.fire.called ).to.be.true; @@ -976,7 +976,7 @@ describe( 'DowncastDispatcher', () => { const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher._convertMarkerAdd( 'name', gyRange, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'name', gyRange, dispatcher._createConversionApi() ); expect( dispatcher.fire.called ).to.be.false; @@ -1013,7 +1013,7 @@ describe( 'DowncastDispatcher', () => { } } ); - dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._createConversionApi() ); expect( spyWholeRange.calledOnce ).to.be.true; expect( spyItems.calledTwice ).to.be.true; @@ -1042,7 +1042,7 @@ describe( 'DowncastDispatcher', () => { } } ); - dispatcher._convertMarkerAdd( 'name', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'name', range, dispatcher._createConversionApi() ); expect( spyItems.called ).to.be.false; @@ -1070,7 +1070,7 @@ describe( 'DowncastDispatcher', () => { } }, { priority: 'high' } ); - dispatcher._convertMarkerAdd( 'marker', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'marker', range, dispatcher._createConversionApi() ); expect( addMarkerSpy.called ).to.be.false; expect( highAddMarkerSpy.calledOnce ).to.be.true; @@ -1099,7 +1099,7 @@ describe( 'DowncastDispatcher', () => { } }, { priority: 'high' } ); - dispatcher._convertMarkerAdd( 'marker', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerAdd( 'marker', range, dispatcher._createConversionApi() ); expect( addMarkerSpy.called ).to.be.false; @@ -1125,7 +1125,7 @@ describe( 'DowncastDispatcher', () => { it( 'should fire removeMarker event', () => { sinon.spy( dispatcher, 'fire' ); - dispatcher._convertMarkerRemove( 'name', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerRemove( 'name', range, dispatcher._createConversionApi() ); expect( dispatcher.fire.calledWith( 'removeMarker:name' ) ).to.be.true; @@ -1137,7 +1137,7 @@ describe( 'DowncastDispatcher', () => { const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher._convertMarkerRemove( 'name', gyRange, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerRemove( 'name', gyRange, dispatcher._createConversionApi() ); expect( dispatcher.fire.called ).to.be.false; @@ -1151,7 +1151,7 @@ describe( 'DowncastDispatcher', () => { const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) ); sinon.spy( dispatcher, 'fire' ); - dispatcher._convertMarkerRemove( 'name', eleRange, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerRemove( 'name', eleRange, dispatcher._createConversionApi() ); expect( dispatcher.fire.called ).to.be.true; @@ -1167,7 +1167,7 @@ describe( 'DowncastDispatcher', () => { expect( data.markerRange.isEqual( range ) ).to.be.true; } ); - dispatcher._convertMarkerRemove( 'name', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerRemove( 'name', range, dispatcher._createConversionApi() ); expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; @@ -1187,7 +1187,7 @@ describe( 'DowncastDispatcher', () => { evt.stop(); }, { priority: 'high' } ); - dispatcher._convertMarkerRemove( 'marker', range, dispatcher._prepareConversionApi() ); + dispatcher._convertMarkerRemove( 'marker', range, dispatcher._createConversionApi() ); expect( removeMarkerSpy.called ).to.be.false; expect( highRemoveMarkerSpy.calledOnce ).to.be.true; From dadf7155691eab309b4d2bbb313442093f7762d2 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 8 Sep 2021 17:07:31 +0200 Subject: [PATCH 031/140] Added tests for deferred unbinding. --- .../tests/conversion/mapper.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/packages/ckeditor5-engine/tests/conversion/mapper.js b/packages/ckeditor5-engine/tests/conversion/mapper.js index 24d60314b87..dfa208ed542 100644 --- a/packages/ckeditor5-engine/tests/conversion/mapper.js +++ b/packages/ckeditor5-engine/tests/conversion/mapper.js @@ -17,6 +17,7 @@ import ViewUIElement from '../../src/view/uielement'; import ViewText from '../../src/view/text'; import ViewPosition from '../../src/view/position'; import ViewRange from '../../src/view/range'; +import ViewDocumentFragment from '../../src/view/documentfragment'; import { StylesProcessor } from '../../src/view/stylesmap'; describe( 'Mapper', () => { @@ -145,6 +146,60 @@ describe( 'Mapper', () => { expect( mapper.toModelElement( viewA ) ).to.be.undefined; expect( mapper.toViewElement( modelA ) ).to.equal( viewB ); } ); + + it( 'should allow deferred unbinding', () => { + const viewA = new ViewElement( viewDocument, 'a' ); + const modelA = new ModelElement( 'a' ); + + const mapper = new Mapper(); + mapper.bindElements( modelA, viewA ); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + + mapper.unbindViewElement( viewA, { defer: true } ); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + + mapper.flushDeferredBindings(); + + expect( mapper.toModelElement( viewA ) ).to.be.undefined; + expect( mapper.toViewElement( modelA ) ).to.be.undefined; + } ); + + it( 'should not unbind if element was reused after deferred unbinding', () => { + const viewA = new ViewElement( viewDocument, 'a' ); + const viewFragmentA = new ViewDocumentFragment( viewDocument, [ viewA ] ); + const viewFragmentB = new ViewDocumentFragment( viewDocument ); + + const modelA = new ModelElement( 'a' ); + + const mapper = new Mapper(); + mapper.bindElements( modelA, viewA ); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + expect( viewA.root ).to.equal( viewFragmentA ); + + mapper.unbindViewElement( viewA, { defer: true } ); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + expect( viewA.root ).to.equal( viewFragmentA ); + + viewFragmentB._appendChild( viewA ); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + expect( viewA.root ).to.equal( viewFragmentB ); + + mapper.flushDeferredBindings(); + + expect( mapper.toModelElement( viewA ) ).to.equal( modelA ); + expect( mapper.toViewElement( modelA ) ).to.equal( viewA ); + expect( viewA.root ).to.equal( viewFragmentB ); + } ); } ); describe( 'Standard mapping', () => { From 6cc2c0cf10d658d4f150cba60ee6486950777896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 9 Sep 2021 22:14:51 +0200 Subject: [PATCH 032/140] Improved the docs. --- .../src/conversion/downcasthelpers.js | 97 +++++++++++++++---- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 2d0ff907b54..9f87d46b1b7 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -98,29 +98,81 @@ export default class DowncastHelpers extends ConversionHelpers { } /** - * Model element to view structure conversion helper. + * Model element to view structure (several elements) conversion helper. * - * This conversion results in creating a view structure with slots for child nodes. For example, model `` becomes - * `
${ slot for table rows }` in the view. + * This conversion results in creating a view structure with defined one or more slots for the child nodes. + * For example, a model `
` may become this structure in the view: * - * Simple slot for all child nodes: + *
+ *
+ * ${ slot for table rows } + *
+ * + * + * The children of the model's `` element will be inserted into the `` element. + * If a `elementToElement()` helper was used, the children would be inserted into the `
`. + * + * An example converter that converts the following model structure: + * + * Some text. + * + * into this sturcture in the view: + * + *
+ *

Some text.

+ *
+ * + * would look like this: * * editor.conversion.for( 'downcast' ).elementToStructure( { * model: 'wrappedParagraph', * view: ( modelElement, conversionApi ) => { * const { writer, slotFor } = conversionApi; * - * const wrapperViewElement = writer.createContainerElement( 'div', { class: 'wrapper' } ); - * const paragraphViewElement = writer.createContainerElement( 'p' ); + * const wrapperViewElement = writer.createContainerElement( 'div', { class: 'wrapper' } ); + * const paragraphViewElement = writer.createContainerElement( 'p' ); * - * writer.insert( writer.createPositionAt( wrapperViewElement, 0 ), paragraphViewElement ); - * writer.insert( writer.createPositionAt( paragraphViewElement, 0 ), slotFor( 'children' ) ); + * writer.insert( writer.createPositionAt( wrapperViewElement, 0 ), paragraphViewElement ); + * writer.insert( writer.createPositionAt( paragraphViewElement, 0 ), slotFor( 'children' ) ); * * return wrapperViewElement; * } * } ); * - * The conversion with slots dedicated for specific model children and with reconversion trigger defined. + * The `slorFor()` function can also take a callback that allows filtering which children of the model element + * should be converted into this slot. + * + * Imagine a table feature where for this model structure: + * + *
+ * ... table cells 1 ... + * ... table cells 2 ... + * ... table cells 3 ... + * + *
Caption text
+ * + * We want to generate this view structure: + * + *
+ * + * + * ... table cells 1 ... + * + * + * ... table cells 2 ... + * ... table cells 3 ... + * + *
+ *
Caption text
+ *
+ * + * The converter has to take `headingRows` attribute into consideration when allocating `` elements + * into the `` and `` elements. Hence, we need two slots and define proper filter callbacks for them. + * + * Additionally, all other elements than `` should be placed outside ``. In the example above, this will + * handle the table caption. + * + * Such a converter would look like this: * * editor.conversion.for( 'downcast' ).elementToStructure( { * model: { @@ -164,6 +216,9 @@ export default class DowncastHelpers extends ConversionHelpers { * } * } ); * + * Note: The children of a model element that's being converted must be allocated in the same order in the view + * in which they are placed in the model. + * * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter * to the conversion process. * @@ -552,7 +607,7 @@ export default class DowncastHelpers extends ConversionHelpers { * // Model: *
[]Foo
* - * // View: + * // View: *

Foo

* * Similarly, when a marker is collapsed after the last element: @@ -2118,9 +2173,9 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { */ /** - * A filtering function used to choose model child nodes to be downcasted into a specific view - * {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor "slot"} while converting - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}. + * A filtering function used to choose model child nodes to be downcasted into the specific view + * {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor "slot"} while executing the + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`} converter. * * @callback module:engine/conversion/downcasthelpers~SlotFilter * @@ -2147,22 +2202,22 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { /** * A helper that creates placeholders for child elements. * - * const viewSlot = conversionApi.slotFor( 'children' ); - * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); + * const viewSlot = conversionApi.slotFor( 'children' ); + * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); * - * conversionApi.writer.insert( viewPosition, viewSlot ); + * conversionApi.writer.insert( viewPosition, viewSlot ); * - * It could be filtered to a specific subset of children: + * It could be filtered to a specific subset of children (only `` model elements in this case): * - * const viewSlot = conversionApi.slotFor( node => node.is( 'element', 'foo' ) ); - * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); + * const viewSlot = conversionApi.slotFor( node => node.is( 'element', 'foo' ) ); + * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 ); * - * conversionApi.writer.insert( viewPosition, viewSlot ); + * conversionApi.writer.insert( viewPosition, viewSlot ); * * While providing a filtered slot make sure to provide slots for all child nodes. A single node can not be downcasted into * multiple slots. * - * **Note**: You should not change an order of nodes, view elements should be in the same order as model nodes. + * **Note**: You should not change the order of nodes. View elements should be in the same order as model nodes. * * @method #slotFor * @param {'children'|module:engine/conversion/downcasthelpers~SlotFilter} modeOrFilter The filter for child nodes. From 728ad0219e7bc40f74339e7a42e98dc91bb11b99 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 17 Sep 2021 19:17:58 +0200 Subject: [PATCH 033/140] DowncastDispatcher events for nested elements fired from the outer event handler. --- .../src/controller/datacontroller.js | 3 +- .../src/controller/editingcontroller.js | 10 ++- .../src/conversion/downcastdispatcher.js | 79 +++++++---------- .../src/conversion/downcasthelpers.js | 86 +++++++++++++------ .../ckeditor5-engine/src/conversion/mapper.js | 10 +++ .../ckeditor5-engine/src/dev-utils/model.js | 2 + .../tests/conversion/downcastdispatcher.js | 9 +- .../tests/conversion/downcasthelpers.js | 11 +++ packages/ckeditor5-heading/src/title.js | 3 + 9 files changed, 130 insertions(+), 83 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js index d63d71af341..a2312b00905 100644 --- a/packages/ckeditor5-engine/src/controller/datacontroller.js +++ b/packages/ckeditor5-engine/src/controller/datacontroller.js @@ -14,7 +14,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import Mapper from '../conversion/mapper'; import DowncastDispatcher from '../conversion/downcastdispatcher'; -import { insertText } from '../conversion/downcasthelpers'; +import { insertAttributesAndChildren, insertText } from '../conversion/downcasthelpers'; import UpcastDispatcher from '../conversion/upcastdispatcher'; import { convertText, convertToModelFragment } from '../conversion/upcasthelpers'; @@ -81,6 +81,7 @@ export default class DataController { schema: model.schema } ); this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } ); + this.downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } ); /** * Upcast dispatcher used by the {@link #set set method}. Upcast converters should be attached to it. diff --git a/packages/ckeditor5-engine/src/controller/editingcontroller.js b/packages/ckeditor5-engine/src/controller/editingcontroller.js index 987500a03f6..dc24a4f4f9f 100644 --- a/packages/ckeditor5-engine/src/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/src/controller/editingcontroller.js @@ -11,7 +11,14 @@ import RootEditableElement from '../view/rooteditableelement'; import View from '../view/view'; import Mapper from '../conversion/mapper'; import DowncastDispatcher from '../conversion/downcastdispatcher'; -import { clearAttributes, convertCollapsedSelection, convertRangeSelection, insertText, remove } from '../conversion/downcasthelpers'; +import { + clearAttributes, + convertCollapsedSelection, + convertRangeSelection, + insertAttributesAndChildren, + insertText, + remove +} from '../conversion/downcasthelpers'; import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; @@ -101,6 +108,7 @@ export default class EditingController { // Attach default model converters. this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } ); + this.downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } ); this.downcastDispatcher.on( 'remove', remove(), { priority: 'low' } ); // Attach default model selection converters. diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index b3aee26998c..b1180d04255 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -272,16 +272,17 @@ export default class DowncastDispatcher { * @fires attribute * @param {module:engine/model/range~Range} range The inserted range. * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. + * @param {Object} [options] TODO */ - _convertInsert( range, conversionApi ) { - const walkerValues = Array.from( range ); - - // Collect a list of things that can be consumed, consisting of nodes and their attributes. - this._addConsumablesForInsert( conversionApi.consumable, walkerValues ); + _convertInsert( range, conversionApi, options = {} ) { + if ( !options.doNotAddConsumables ) { + // Collect a list of things that can be consumed, consisting of nodes and their attributes. + this._addConsumablesForInsert( conversionApi.consumable, Array.from( range ) ); + } // Fire a separate insert event for each node and text fragment contained in the range. - for ( const data of walkerValues.map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( data, conversionApi ); + for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { + this.fire( getEventName( 'insert', data ), data, conversionApi ); } } @@ -317,17 +318,15 @@ export default class DowncastDispatcher { // Create a separate attribute event for each node in the range. for ( const value of range ) { - const item = value.item; - const itemRange = Range._createFromPositionAndShift( value.previousPosition, value.length ); const data = { - item, - range: itemRange, + item: value.item, + range: Range._createFromPositionAndShift( value.previousPosition, value.length ), attributeKey: key, attributeOldValue: oldValue, attributeNewValue: newValue }; - this._testAndFire( `attribute:${ key }`, data, conversionApi ); + this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); } } @@ -353,7 +352,7 @@ export default class DowncastDispatcher { // Fire a separate insert event for each node and text fragment contained shallowly in the range. for ( const data of walkerValues.map( walkerValueToEventData ) ) { - this._convertInsertWithAttributes( { ...data, reconversion: true }, conversionApi ); + this.fire( getEventName( 'insert', data ), { ...data, reconversion: true }, conversionApi ); } } @@ -513,22 +512,23 @@ export default class DowncastDispatcher { } /** - * Tests passed `consumable` to check whether given event can be fired and if so, fires it. + * TODO * * @private - * @fires insert - * @fires attribute - * @param {String} type Event type. - * @param {Object} data Event data. - * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ - _testAndFire( type, data, conversionApi ) { - if ( !conversionApi.consumable.test( data.item, type ) ) { - // Do not fire event if the item was consumed. - return; - } + _fireAddAttributes( item, conversionApi ) { + const data = { + item, + range: Range._createOn( item ) + }; + + for ( const key of data.item.getAttributeKeys() ) { + data.attributeKey = key; + data.attributeOldValue = null; + data.attributeNewValue = data.item.getAttribute( key ); - this.fire( getEventName( type, data ), data, conversionApi ); + this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); + } } /** @@ -546,36 +546,15 @@ export default class DowncastDispatcher { consumable: new Consumable(), writer, options, - convertInsert: range => this._convertInsert( range, conversionApi ) + // TODO docs for those methods in DowncastConversionApi + convertItem: modelItem => this._convertInsert( Range._createOn( modelItem ), conversionApi ), + convertChildren: modelItem => this._convertInsert( Range._createIn( modelItem ), conversionApi, { doNotAddConsumables: true } ), + convertAttributes: modelItem => this._fireAddAttributes( modelItem, conversionApi ) }; return conversionApi; } - /** - * Internal method for converting element insertion. It will fire events for the inserted element and events for its attributes. - * - * @private - * @fires insert - * @fires attribute - * @param {Object} data Event data. - * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. - */ - _convertInsertWithAttributes( data, conversionApi ) { - this._testAndFire( 'insert', data, conversionApi ); - - // Fire a separate addAttribute event for each attribute that was set on inserted items. - // This is important because most attributes converters will listen only to add/change/removeAttribute events. - // If we would not add this part, attributes on inserted nodes would not be converted. - for ( const key of data.item.getAttributeKeys() ) { - data.attributeKey = key; - data.attributeOldValue = null; - data.attributeNewValue = data.item.getAttribute( key ); - - this._testAndFire( `attribute:${ key }`, data, conversionApi ); - } - } - /** * Fired to enable reducing (transforming) changes buffered in the {@link module:engine/model/differ~Differ `Differ`} before * {@link #convertChanges `convertChanges()`} will fire any conversion events. diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 9f87d46b1b7..bfe52023f02 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -673,7 +673,7 @@ export default class DowncastHelpers extends ConversionHelpers { */ export function insertText() { return ( evt, data, conversionApi ) => { - if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; } @@ -685,6 +685,19 @@ export function insertText() { }; } +/** + * TODO + */ +export function insertAttributesAndChildren() { + return ( evt, data, conversionApi ) => { + conversionApi.convertAttributes( data.item ); + + if ( data.item.is( 'element' ) && !data.item.isEmpty ) { + conversionApi.convertChildren( data.item ); + } + }; +} + /** * Function factory that creates a default downcast converter for node remove changes. * @@ -888,6 +901,10 @@ export function clearAttributes() { */ export function wrap( elementCreator ) { return ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.test( data.item, evt.name ) ) { + return; + } + // Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed // or the attribute was removed. const oldViewElement = elementCreator( data.attributeOldValue, conversionApi ); @@ -952,13 +969,17 @@ export function wrap( elementCreator ) { */ export function insertElement( elementCreator ) { return ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.test( data.item, evt.name ) ) { + return; + } + const viewElement = elementCreator( data.item, conversionApi ); if ( !viewElement ) { return; } - if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; } @@ -986,6 +1007,10 @@ export function insertElement( elementCreator ) { */ export function insertStructure( elementCreator, consumer ) { return ( evt, data, conversionApi ) => { + if ( !consumer( data.item, conversionApi.consumable, { preflight: true } ) ) { + return; + } + const slotsMap = new Map(); // View creation. @@ -1276,6 +1301,10 @@ function removeMarkerData( viewCreator ) { // @returns {Function} Set/change attribute converter. function changeAttribute( attributeCreator ) { return ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.test( data.item, evt.name ) ) { + return; + } + const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi ); const newAttribute = attributeCreator( data.attributeNewValue, conversionApi ); @@ -1902,24 +1931,24 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) { // @param {String} model.name The name of element. // @param {Array.} model.attributes The list of attribute names that should trigger reconversion. // @param {Boolean} [model.children] Whether the child list change should trigger reconversion. -// @returns {Function} +// @returns {Boolean} function createChangeReducerCallback( model ) { return ( node, change ) => { if ( !node.is( 'element', model.name ) ) { - return null; + return false; } if ( change.type == 'attribute' ) { - if ( !model.attributes.includes( change.attributeKey ) ) { - return null; + if ( model.attributes.includes( change.attributeKey ) ) { + return true; } } else { - if ( !model.children ) { - return null; + if ( model.children ) { + return true; } } - return [ node ]; + return false; }; } @@ -1929,9 +1958,10 @@ function createChangeReducerCallback( model ) { // @param {String} model.name The name of element. // @param {Array.} model.attributes The list of attribute names that should trigger reconversion. // @param {Boolean} [model.children] Whether the child list change should trigger reconversion. -// @param {Function} The callback for checking a single diff item whether it should trigger reconversion. // @returns {Function} -function createChangeReducer( model, callback = createChangeReducerCallback( model ) ) { +function createChangeReducer( model ) { + const shouldReplace = createChangeReducerCallback( model ); + return ( evt, data ) => { const reducedChanges = []; @@ -1944,32 +1974,28 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod // For insert or remove use parent element because we need to check if it's added/removed child. const node = change.position ? change.position.parent : change.range.start.nodeAfter; - const elements = callback( node, change ); - - if ( !elements ) { + // TODO Node might not be here if it's change of an attribute in the middle of some text. + // undoediting-integration.js line 323 fails without this check + if ( !node || !shouldReplace( node, change ) ) { reducedChanges.push( change ); continue; } - for ( const element of elements ) { - // If it's already marked for reconversion, so skip this change. - if ( data.reconvertedElements.has( element ) ) { - continue; - } - - data.reconvertedElements.add( element ); + // If it's already marked for reconversion, so skip this change, otherwise add the diff items. + if ( !data.reconvertedElements.has( node ) ) { + data.reconvertedElements.add( node ); - const position = ModelPosition._createBefore( element ); + const position = ModelPosition._createBefore( node ); reducedChanges.push( { type: 'remove', - name: element.name, + name: node.name, position, length: 1 }, { type: 'reinsert', - name: element.name, + name: node.name, position, length: 1 } ); @@ -1988,7 +2014,7 @@ function createChangeReducer( model, callback = createChangeReducerCallback( mod // @param {Boolean} [model.children] Whether the child list change should trigger reconversion. // @returns {module:engine/conversion/downcasthelpers~ConsumerFunction} function createConsumer( model ) { - return ( node, consumable ) => { + return ( node, consumable, options = {} ) => { const events = [ 'insert' ]; // Collect all set attributes that are triggering conversion. @@ -2002,7 +2028,11 @@ function createConsumer( model ) { return false; } - return events.every( event => consumable.consume( node, event ) ); + if ( !options.preflight ) { + events.forEach( event => consumable.consume( node, event ) ); + } + + return true; }; } @@ -2011,7 +2041,7 @@ function createConsumer( model ) { // @param {module:engine/model/element~Element} element // @param {Map.>} slotsMap // @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @returns {Function} +// @returns {Function} Exposed by conversionApi as {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi#slotFor}. function createSlotFactory( element, slotsMap, conversionApi ) { return modeOrFilter => { const slot = conversionApi.writer.createContainerElement( '$slot' ); @@ -2132,7 +2162,7 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) ); } else { - conversionApi.convertInsert( ModelRange._createOn( modelChildNode ) ); + conversionApi.convertItem( modelChildNode ); } } } diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 04c304b1ab5..6112e0726ee 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -15,6 +15,7 @@ import ViewRange from '../view/range'; import ViewText from '../view/text'; import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; /** @@ -113,6 +114,15 @@ export default class Mapper { const viewContainer = this._modelToViewMapping.get( data.modelPosition.parent ); + if ( !viewContainer ) { + /** + * TODO + * + * @error mapping-view-position-parent-not-found + */ + throw new CKEditorError( 'mapping-view-position-parent-not-found', this, { modelPosition: data.modelPosition } ); + } + data.viewPosition = this.findPositionIn( viewContainer, data.modelPosition.offset ); }, { priority: 'low' } ); diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js index 91f076c190c..c52934d63ff 100644 --- a/packages/ckeditor5-engine/src/dev-utils/model.js +++ b/packages/ckeditor5-engine/src/dev-utils/model.js @@ -31,6 +31,7 @@ import Mapper from '../conversion/mapper'; import { convertCollapsedSelection, convertRangeSelection, + insertAttributesAndChildren, insertElement, insertText, insertUIElement, @@ -233,6 +234,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu mapper.bindElements( node.root, viewRoot ); downcastDispatcher.on( 'insert:$text', insertText() ); + downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } ); downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => { if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) { const converter = wrap( ( modelAttributeValue, { writer } ) => { diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index f8b799e3b91..e059e3a3372 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -17,6 +17,7 @@ import View from '../../src/view/view'; import ViewContainerElement from '../../src/view/containerelement'; import DowncastWriter from '../../src/view/downcastwriter'; import { StylesProcessor } from '../../src/view/stylesmap'; +import { insertAttributesAndChildren } from '../../src/conversion/downcasthelpers'; describe( 'DowncastDispatcher', () => { let dispatcher, doc, root, differStub, model, view, mapper, apiObj; @@ -30,6 +31,8 @@ describe( 'DowncastDispatcher', () => { dispatcher = new DowncastDispatcher( { mapper, apiObj } ); root = doc.createRoot(); + dispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } ); + differStub = { getMarkersToRemove: () => [], getChanges: () => [], @@ -374,10 +377,10 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:title:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; + expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.false; expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; @@ -535,10 +538,10 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:title:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.true; expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; + expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.false; expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; @@ -571,7 +574,7 @@ describe( 'DowncastDispatcher', () => { dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { if ( conversionApi.consumable.consume( data.item, 'insert' ) ) { - conversionApi.convertInsert( data.range ); + conversionApi.convertItem( data.item ); } } ); diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 297dd40d4de..face48deaba 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -26,6 +26,7 @@ import DowncastHelpers, { convertCollapsedSelection, convertRangeSelection, createViewElementFromHighlightDescriptor, + insertAttributesAndChildren, insertText } from '../../src/conversion/downcasthelpers'; @@ -181,6 +182,10 @@ describe( 'DowncastHelpers', () => { view: () => null } ); + controller.downcastDispatcher.on( 'insert:heading', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + }, { priority: 'lowest' } ); + model.change( writer => { writer.insertElement( 'heading', {}, modelRoot, 0 ); } ); @@ -1692,6 +1697,10 @@ describe( 'DowncastHelpers', () => { it( 'should not convert if creator returned null', () => { downcastHelpers.elementToElement( { model: 'div', view: () => null } ); + controller.downcastDispatcher.on( 'insert:div', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + }, { priority: 'lowest' } ); + const modelElement = new ModelElement( 'div' ); model.change( writer => { @@ -2855,6 +2864,7 @@ describe( 'DowncastHelpers', () => { const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); conversionApi.writer.insert( viewPosition, viewText ); + conversionApi.consumable.consume( data.item, evt.name ); } ); // added so it can store selection, otherwise it throws. @@ -3803,6 +3813,7 @@ describe( 'downcast selection converters', () => { dispatcher = new DowncastDispatcher( { mapper, viewSelection } ); dispatcher.on( 'insert:$text', insertText() ); + dispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } ); downcastHelpers = new DowncastHelpers( [ dispatcher ] ); downcastHelpers.attributeToElement( { model: 'bold', view: 'strong' } ); diff --git a/packages/ckeditor5-heading/src/title.js b/packages/ckeditor5-heading/src/title.js index e93b926689e..92a6a2d8d50 100644 --- a/packages/ckeditor5-heading/src/title.js +++ b/packages/ckeditor5-heading/src/title.js @@ -87,6 +87,9 @@ export default class Title extends Plugin { // Conversion. editor.conversion.for( 'downcast' ).elementToElement( { model: 'title-content', view: 'h1' } ); + editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert:title', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ) ); // Custom converter is used for data v -> m conversion to avoid calling post-fixer when setting data. // See https://github.com/ckeditor/ckeditor5/issues/2036. editor.data.upcastDispatcher.on( 'element:h1', dataViewModelH1Insertion, { priority: 'high' } ); From 8609f32f84a6ccf8d12d4650abae422f9ab9d0bb Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 17 Sep 2021 19:22:15 +0200 Subject: [PATCH 034/140] Reverted not needed change. --- packages/ckeditor5-heading/src/title.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/ckeditor5-heading/src/title.js b/packages/ckeditor5-heading/src/title.js index 92a6a2d8d50..e93b926689e 100644 --- a/packages/ckeditor5-heading/src/title.js +++ b/packages/ckeditor5-heading/src/title.js @@ -87,9 +87,6 @@ export default class Title extends Plugin { // Conversion. editor.conversion.for( 'downcast' ).elementToElement( { model: 'title-content', view: 'h1' } ); - editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert:title', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, evt.name ); - } ) ); // Custom converter is used for data v -> m conversion to avoid calling post-fixer when setting data. // See https://github.com/ckeditor/ckeditor5/issues/2036. editor.data.upcastDispatcher.on( 'element:h1', dataViewModelH1Insertion, { priority: 'high' } ); From 7fbea50bc185320ee4a1fce78aeab8e2ef156bd6 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Mon, 20 Sep 2021 08:54:30 +0200 Subject: [PATCH 035/140] WIP. Add elementToElement reconversion support --- .../src/conversion/downcasthelpers.js | 52 +++++++++++-------- .../ckeditor5-engine/src/dev-utils/model.js | 2 +- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 9f87d46b1b7..a18e5a42394 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -53,24 +53,30 @@ export default class DowncastHelpers extends ConversionHelpers { * } ); * * editor.conversion.for( 'downcast' ).elementToElement( { - * model: 'heading', + * model: { + * name: 'paragraph', + * attributes: [ 'foo' ] + * }, * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; * * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ); * } * } ); - * - * The element-to-element conversion supports the reconversion mechanism. This is helpful in the conversion to complex view structures - * where multiple atomic element-to-element and attribute-to-attribute or attribute-to-element could be used. By specifying - * `triggerBy()` events you can trigger reconverting the model to full view tree structures at once. + + Children + + -> + +
+

Children

* * editor.conversion.for( 'downcast' ).elementToElement( { - * model: 'complex', - * view: ( modelElement, conversionApi ) => createComplexViewFromModel( modelElement, conversionApi ), - * triggerBy: { - * attributes: [ 'foo', 'bar' ], - * children: [ 'slot' ] + * model: 'heading', + * view: ( modelElement, conversionApi ) => { + * const { writer } = conversionApi; + * + * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ); * } * } ); * @@ -86,11 +92,6 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} * as parameters and returns a view container element. - * @param {Object} [config.triggerBy] Reconversion triggers. At least one trigger must be defined. - * @param {Array.} config.triggerBy.attributes The name of the element's attributes whose change will trigger element - * reconversion. - * @param {Array.} config.triggerBy.children The name of direct children whose adding or removing will trigger element - * reconversion. * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers} */ elementToElement( config ) { @@ -950,7 +951,7 @@ export function wrap( elementCreator ) { * @param {Function} elementCreator Function returning a view element, which will be inserted. * @returns {Function} Insert element event converter. */ -export function insertElement( elementCreator ) { +export function insertElement( elementCreator, consumer ) { return ( evt, data, conversionApi ) => { const viewElement = elementCreator( data.item, conversionApi ); @@ -958,7 +959,8 @@ export function insertElement( elementCreator ) { return; } - if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) { + // Consume an element insertion and all present attributes that are specified as a reconversion triggers. + if ( !consumer( data.item, conversionApi.consumable ) ) { return; } @@ -966,6 +968,8 @@ export function insertElement( elementCreator ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); + + reinsertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } @@ -1554,17 +1558,23 @@ function removeHighlight( highlightDescriptor ) { // @param {Object} config Conversion configuration. // @param {String} config.model // @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view -// @param {Object} [config.triggerBy] -// @param {Array.} [config.triggerBy.attributes] -// @param {Array.} [config.triggerBy.children] // @returns {Function} Conversion helper. function downcastElementToElement( config ) { config = cloneDeep( config ); + config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); return dispatcher => { - dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } ); + dispatcher.on( + 'insert:' + config.model.name, + insertElement( config.view, createConsumer( config.model ) ), + { priority: config.converterPriority || 'normal' } + ); + + if ( config.model.children || config.model.attributes.length ) { + dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); + } }; } diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js index 91f076c190c..caa7689a733 100644 --- a/packages/ckeditor5-engine/src/dev-utils/model.js +++ b/packages/ckeditor5-engine/src/dev-utils/model.js @@ -250,7 +250,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu const attributes = convertAttributes( modelItem.getAttributes(), stringifyAttributeValue ); return new ViewContainerElement( viewDocument, modelItem.name, attributes ); - } ) ); + }, ( node, consumable ) => consumable.consume( node, 'insert' ) ) ); downcastDispatcher.on( 'selection', convertRangeSelection() ); downcastDispatcher.on( 'selection', convertCollapsedSelection() ); From a2cc96cde99ca3189f2484d47d3db04ef0a36ad6 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Mon, 20 Sep 2021 10:00:00 +0200 Subject: [PATCH 036/140] Add tests to elementToElement conversion helper. --- .../tests/conversion/downcasthelpers.js | 524 ++++++++++++++++++ 1 file changed, 524 insertions(+) diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 297dd40d4de..dd19fa18277 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -118,6 +118,530 @@ describe( 'DowncastHelpers', () => { expectResult( '

' ); } ); + + it( 'config.view is a function that does not return view element', () => { + downcastHelpers.elementToElement( { + model: 'heading', + view: () => null + } ); + + model.change( writer => { + writer.insertElement( 'heading', {}, modelRoot, 0 ); + } ); + + expectResult( '' ); + } ); + + describe( 'converting element together with selected attributes', () => { + it( 'should allow passing a list of attributes to convert and consume', () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToElement( { + model: { + name: 'simpleBlock', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + + let consumable; + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + }, { priority: 'low' } ); + + setModelData( model, '' ); + + expectResult( '
' ); + + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toStyle' ) ).to.be.false; + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toClass' ) ).to.be.null; + } ); + + it( 'should allow passing a single attribute name to convert and consume', () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToElement( { + model: { + name: 'simpleBlock', + attributes: 'toStyle' + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + + let consumable; + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + }, { priority: 'low' } ); + + setModelData( model, '' ); + + expectResult( '
' ); + + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toStyle' ) ).to.be.false; + expect( consumable.test( modelRoot.getChild( 0 ), 'attribute:toClass' ) ).to.be.null; + } ); + } ); + + describe( 'with simple block view structure (without reconvertion on children list change)', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToElement( { + model: { + name: 'simpleBlock', + attributes: [ 'toStyle', 'toClass' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + } + } ); + } ); + + it( 'should convert on insert', () => { + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + model.change( writer => { + writer.insertElement( 'simpleBlock', modelRoot, 0 ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on attribute set', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + expect( viewAfter ).to.not.equal( viewBefore ); + } ); + + it( 'should convert on attribute change', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + } ); + + it( 'should convert on attribute remove', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should convert on one attribute add and other remove', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + model.change( writer => { + writer.removeAttribute( 'toStyle', modelRoot.getChild( 0 ) ); + writer.setAttribute( 'toClass', true, modelRoot.getChild( 0 ) ); + } ); + + expectResult( '
' ); + } ); + + it( 'should properly re-bind mapper mappings and retain markers', () => { + downcastHelpers.elementToElement( { + model: 'simpleBlock', + view: ( modelElement, { writer } ) => { + const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); + + return toWidget( viewElement, writer ); + }, + triggerBy: { + attributes: [ 'toStyle', 'toClass' ] + }, + converterPriority: 'high' + } ); + + const mapper = controller.mapper; + + downcastHelpers.markerToHighlight( { + model: 'myMarker', + view: { classes: 'foo' } + } ); + + setModelData( model, '' ); + + const modelElement = modelRoot.getChild( 0 ); + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.addMarker( 'myMarker', { range: writer.createRangeOn( modelElement ), usingOperation: false } ); + } ); + + expect( mapper.toViewElement( modelElement ) ).to.equal( viewBefore ); + expect( mapper.toModelElement( viewBefore ) ).to.equal( modelElement ); + expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:block', modelElement ); + } ); + + const [ viewAfter ] = getNodes(); + + expect( mapper.toViewElement( modelElement ) ).to.equal( viewAfter ); + expect( mapper.toModelElement( viewBefore ) ).to.be.undefined; + expect( mapper.toModelElement( viewAfter ) ).to.equal( modelElement ); + expect( mapper.markerNameToElements( 'myMarker' ).has( viewAfter ) ).to.be.true; + expect( mapper.markerNameToElements( 'myMarker' ).has( viewBefore ) ).to.be.false; + } ); + + it( 'should not reconvert if non watched attribute has changed', () => { + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'notTriggered', true, modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '
' ); + + expect( viewAfter ).to.equal( viewBefore ); + } ); + + it( 'should not reconvert on child element added', () => { + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + + setModelData( model, '' ); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + const [ viewBefore ] = getNodes(); + + model.change( writer => { + writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); + } ); + + const [ viewAfter ] = getNodes(); + + expectResult( '

' ); + + expect( viewAfter ).to.equal( viewBefore ); + } ); + } ); + + describe( 'with simple block view structure (with reconvertion on child add)', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root' + } ); + + downcastHelpers.elementToElement( { + model: { + name: 'simpleBlock', + children: true + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div' ); + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert on insert', () => { + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.not.have.property( 'reconversion' ); + } ); + + model.change( writer => { + const simpleBlock = writer.createElement( 'simpleBlock' ); + const paragraph = writer.createElement( 'paragraph' ); + + writer.insert( simpleBlock, modelRoot, 0 ); + writer.insert( paragraph, simpleBlock, 0 ); + writer.insertText( 'foo', paragraph, 0 ); + } ); + + expectResult( '

foo

' ); + } ); + + it( 'should convert on adding a child (at the beginning)', () => { + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter ] = getNodes(); + + expectResult( '

bar

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; + } ); + + it( 'should convert on adding a child (in the middle)', () => { + setModelData( model, + '' + + 'foo' + + 'bar' + + '' + ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ viewBefore, paraFooBefore, textFooBefore, paraBarBefore, textBarBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'baz' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, + paraFooAfter, textFooAfter, /* insertedPara */, /* insertedText */, paraBarAfter, textBarAfter + ] = getNodes(); + + expectResult( '

foo

baz

bar

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraFooAfter, 'para foo' ).to.equal( paraFooBefore ); + expect( textFooAfter, 'text foo' ).to.equal( textFooBefore ); + expect( paraBarAfter, 'para bar' ).to.equal( paraBarBefore ); + expect( textBarAfter, 'text bar' ).to.equal( textBarBefore ); + expect( spy.called ).to.be.true; + } ); + + it( 'should convert on adding a child (at the end)', () => { + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

bar

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; + } ); + + it( 'should convert on removing a child', () => { + setModelData( model, + 'foobar' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + spy(); + } ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.called ).to.be.true; + } ); + + // https://github.com/ckeditor/ckeditor5/issues/9641 + it( 'should convert on multiple similar child hooks', () => { + model.schema.register( 'simpleBlock2', { + allowIn: '$root', + allowChildren: 'paragraph' + } ); + + downcastHelpers.elementToElement( { + model: { + name: 'simpleBlock2', + children: true + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', { class: 'second' } ); + } + } ); + + setModelData( model, + 'foo' + + 'bar' + ); + + const [ viewBefore0, paraBefore0, textBefore0 ] = getNodes( 0 ); + const [ viewBefore1, paraBefore1, textBefore1 ] = getNodes( 1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'abc' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter0, paraAfter0, textAfter0 ] = getNodes( 0 ); + const [ viewAfter1, paraAfter1, textAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( '123' ); + + writer.insert( paragraph, modelRoot.getChild( 1 ), 1 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfterAfter0, paraAfterAfter0, textAfterAfter0 ] = getNodes( 0 ); + const [ viewAfterAfter1, paraAfterAfter1, textAfterAfter1 ] = getNodes( 1 ); + + expectResult( + '

foo

abc

' + + '

bar

123

' + ); + + expect( viewAfter0, 'simpleBlock' ).to.not.equal( viewBefore0 ); + expect( paraAfter0, 'para' ).to.equal( paraBefore0 ); + expect( textAfter0, 'text' ).to.equal( textBefore0 ); + + expect( viewAfter1, 'simpleBlock' ).to.equal( viewBefore1 ); + expect( paraAfter1, 'para' ).to.equal( paraBefore1 ); + expect( textAfter1, 'text' ).to.equal( textBefore1 ); + + expect( viewAfterAfter0, 'simpleBlock' ).to.equal( viewAfter0 ); + expect( paraAfterAfter0, 'para' ).to.equal( paraAfter0 ); + expect( textAfterAfter0, 'text' ).to.equal( textAfter0 ); + + expect( viewAfterAfter1, 'simpleBlock' ).to.not.equal( viewAfter1 ); + expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); + expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); + } ); + } ); } ); describe( 'elementToStructure()', () => { From 6117bf7e7290e6feb1917dcafa9d8eabde32e6aa Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Mon, 20 Sep 2021 10:11:46 +0200 Subject: [PATCH 037/140] Docs cleanup. Add elementToElement reconversion example to docs. --- .../src/conversion/downcasthelpers.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index a18e5a42394..72dca9447c0 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -53,23 +53,13 @@ export default class DowncastHelpers extends ConversionHelpers { * } ); * * editor.conversion.for( 'downcast' ).elementToElement( { - * model: { - * name: 'paragraph', - * attributes: [ 'foo' ] - * }, + * model: 'heading', * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; * * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ); * } * } ); - - Children - - -> - -
-

Children

* * editor.conversion.for( 'downcast' ).elementToElement( { * model: 'heading', @@ -80,6 +70,19 @@ export default class DowncastHelpers extends ConversionHelpers { * } * } ); * + * editor.conversion.for( 'downcast' ).elementToElement( { + * model: { + * name: 'simpleBox', + * attributes: [ 'foo' ], + * children: true + * }, + * view: ( modelElement, conversionApi ) => { + * const { writer } = conversionApi; + * + * return writer.createContainerElement( 'div' ); + * } + * } ); + * * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter * to the conversion process. * From 475bcb38ad8d29851f5346011124024a833797cc Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 20 Sep 2021 13:45:43 +0200 Subject: [PATCH 038/140] Do not fire events for reconverted element children. --- .../src/conversion/downcastdispatcher.js | 11 +++++++---- .../src/conversion/downcasthelpers.js | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index b1180d04255..6b38fbe9eb9 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -523,11 +523,14 @@ export default class DowncastDispatcher { }; for ( const key of data.item.getAttributeKeys() ) { - data.attributeKey = key; - data.attributeOldValue = null; - data.attributeNewValue = data.item.getAttribute( key ); + // Do not fire attribute events for just added nodes that consumed attributes. + if ( conversionApi.consumable.test( data.item, `attribute:${ key }` ) ) { + data.attributeKey = key; + data.attributeOldValue = null; + data.attributeNewValue = data.item.getAttribute( key ); - this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); + this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); + } } } diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index bfe52023f02..26d87fb17cd 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -690,6 +690,10 @@ export function insertText() { */ export function insertAttributesAndChildren() { return ( evt, data, conversionApi ) => { + if ( data.reconversion ) { + return; + } + conversionApi.convertAttributes( data.item ); if ( data.item.is( 'element' ) && !data.item.isEmpty ) { From 9e330d0fabd6b1f77d33f562f56169d2b1fcfa4f Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 21 Sep 2021 09:00:18 +0200 Subject: [PATCH 039/140] Add manual test for elementToElement reconversion. --- .../tests/manual/elementreconversion.html | 93 +++++++++++ .../tests/manual/elementreconversion.js | 153 ++++++++++++++++++ .../tests/manual/elementreconversion.md | 38 +++++ 3 files changed, 284 insertions(+) create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.html create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.js create mode 100644 packages/ckeditor5-engine/tests/manual/elementreconversion.md diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.html b/packages/ckeditor5-engine/tests/manual/elementreconversion.html new file mode 100644 index 00000000000..1a6bfcdf0aa --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.html @@ -0,0 +1,93 @@ + + + + +
+ Mode: + + + +
+ +
+
+
+
+
diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js new file mode 100644 index 00000000000..267ccaad040 --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.js @@ -0,0 +1,153 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, window, document */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; + +const thresholds = new Map(); + +thresholds.set( 15, 'huge' ); +thresholds.set( 10, 'high' ); +thresholds.set( 7, 'reasonable' ); +thresholds.set( 4, 'few' ); +thresholds.set( 2, 'little' ); +thresholds.set( 1, 'single' ); + +const getThreshold = value => { + for ( const [ thresholdValue, name ] of thresholds ) { + if ( value >= thresholdValue ) { + return name; + } + } +}; + +function Items( editor ) { + editor.model.schema.register( 'items', { + allowIn: '$root', + allowAttributes: [ 'mode' ], + allowChildren: [ 'item' ] + } ); + + editor.model.schema.register( 'item', { + allowChildren: [ '$text' ] + } ); + + editor.conversion.for( 'downcast' ).elementToElement( { + model: { + name: 'items', + attributes: [ 'mode' ], + children: true + }, + view: ( modelElement, { writer } ) => { + const mode = modelElement.getAttribute( 'mode' ); + const attributes = { class: 'items ' }; + + if ( mode === 'threshold' ) { + return writer.createContainerElement( 'div', { + 'data-amount': getThreshold( modelElement.childCount ), + ...attributes + } ); + } + + if ( mode === 'hsl' ) { + return writer.createContainerElement( 'div', { + style: `background-color: hsl(${ modelElement.childCount * 5 }, 100%, 50%)`, + ...attributes + } ); + } + + return writer.createContainerElement( 'div', attributes ); + } + } ); + + editor.conversion.for( 'downcast' ).elementToElement( { + model: 'item', + view: { name: 'div', classes: 'item' } + } ); + + editor.conversion.for( 'upcast' ).elementToElement( { + view: { name: 'div', classes: 'items' }, + model: ( viewElement, { writer } ) => { + return writer.createElement( 'items', { + mode: viewElement.getAttribute( 'data-mode' ) + } ); + } + } ); + + editor.conversion.for( 'upcast' ).elementToElement( { + view: { name: 'div', classes: 'item' }, + model: 'item' + } ); +} + +function AddRenderCount( editor ) { + let insertCount = 0; + + const nextInsert = () => insertCount++; + + editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert', ( event, data, conversionApi ) => { + const view = conversionApi.mapper.toViewElement( data.item ); + + if ( view ) { + const insertCount = nextInsert(); + console.log( data, insertCount ); + + conversionApi.writer.setAttribute( 'data-insert-count', `${ insertCount }`, view ); + conversionApi.writer.setAttribute( 'title', `Insertion counter: ${ insertCount }`, view ); + } + }, { priority: 'lowest' } ) ); +} + +ClassicEditor + .create( document.querySelector( '#editor' ), { + plugins: [ ArticlePluginSet, Items, AddRenderCount ], + toolbar: [ + 'heading', + 'bold', + 'italic', + 'link', + 'bulletedList', + 'numberedList', + '|', + 'outdent', + 'indent', + '|', + 'blockQuote', + 'insertTable', + 'mediaEmbed', + 'undo', + 'redo' + ], + image: { + toolbar: [ 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + } + } ) + .then( editor => { + window.editor = editor; + + for ( const option of document.querySelectorAll( 'input[name=mode]' ) ) { + option.addEventListener( 'change', event => { + editor.model.change( writer => { + writer.setAttribute( + 'mode', + event.target.value, + editor.model.document.getRoot().getChild( 0 ) + ); + } ); + } ); + } + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.md b/packages/ckeditor5-engine/tests/manual/elementreconversion.md new file mode 100644 index 00000000000..0e7be98c661 --- /dev/null +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.md @@ -0,0 +1,38 @@ +# Parent element reconversion + +The editor should be loaded with `items` element that contains one `item` element in which user can edit content. + +Adding new items (by pressing Enter key) or removing (by pressing backspace) should: + +### In threshold mode + +Update parent `items` view element's `data-amount` attribute and change the list background color accordingly. + +List of threesholds: + +``` +| Items | Amount | Color | +|----------|------------|-----------------| +| 1 item | single | aquamarine | +| 2 items | little | cadetblue | +| 4 items | few | darkkhaki | +| 7 items | reasonable | darksalmon | +| 10 items | high | deeppink | +| 15 items | huge | mediumvioletred | +``` + +### In HSL mode + +Update parent `items` view element's inline style background color to hsl value with hue shifted by 5 every time an `item` is addded or removed. + +### In none mode + +Remove background color and update nothing. + +## Reconversion couter + +In every mode you should be able to inspect number of times each item has been inserted. + +This way you can observe increasing insertion counter on the main `items` element every time a child is either inserted or removed. + +Main `items` element counter should also be increased when you change mode. From 2ab03cd6c963f464205d5b031be9243560ad8928 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 21 Sep 2021 09:00:56 +0200 Subject: [PATCH 040/140] Remove console log. --- packages/ckeditor5-engine/tests/manual/elementreconversion.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js index 267ccaad040..5c7adabab9c 100644 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.js +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.js @@ -94,7 +94,6 @@ function AddRenderCount( editor ) { if ( view ) { const insertCount = nextInsert(); - console.log( data, insertCount ); conversionApi.writer.setAttribute( 'data-insert-count', `${ insertCount }`, view ); conversionApi.writer.setAttribute( 'title', `Insertion counter: ${ insertCount }`, view ); From dbe3e2562e501212442942e587f34cdb282e63b7 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 21 Sep 2021 09:33:43 +0200 Subject: [PATCH 041/140] Add reconversion examples in jsdocs for elementToElement helper. --- .../src/conversion/downcasthelpers.js | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 72dca9447c0..4f9d3774a6f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -70,16 +70,75 @@ export default class DowncastHelpers extends ConversionHelpers { * } * } ); * + * In order to reconvert parent element if any of its direct children have been added or removed use `children` property on a `model` + * description. For example, model: + * + * Some text. + * + * will be converted into this sturcture in the view: + * + *
+ *

Some text.

+ *
+ * + * But if more items inserted in the model: + * + * Some text.Other item. + * + * it will be converted into this sturcture in the view (note the parent element `data-type` change): + * + *
+ *

Some text.

+ *

Other item.

+ *
+ * + * Such a converter would look like this: + * * editor.conversion.for( 'downcast' ).elementToElement( { * model: { - * name: 'simpleBox', - * attributes: [ 'foo' ], + * name: 'items', * children: true * }, * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; * - * return writer.createContainerElement( 'div' ); + * if ( modelElement.childCount === 1 ) {} + * return writer.createContainerElement( 'div', { class: 'items', 'data-type': 'single' } ); + * } + * + * return writer.createContainerElement( 'div', { class: 'items', 'data-type': 'multiple' } ); + * } + * } ); + * + * In order to reconvert parent element if any of its attributes have been updated use `attributes` property on a `model` + * description. For example, model: + * + * Some text. + * + * will be converted into this sturcture in the view: + * + *
+ *

Some text.

+ *
+ * + * But if `items` element type attribute has been updated to `advanced` for example, then + * it will be converted into this sturcture in the view (note the parent element `data-type` change): + * + *
+ *

Some text.

+ *
+ * + * Such a converter would look like this: + * + * editor.conversion.for( 'downcast' ).elementToElement( { + * model: { + * name: 'items', + * attributes: [ 'type' ] + * }, + * view: ( modelElement, conversionApi ) => { + * const { writer } = conversionApi; + * + * return writer.createContainerElement( 'div', { class: 'items', 'data-type': modelElement.getAttribute( 'type' ) } ); * } * } ); * @@ -91,7 +150,11 @@ export default class DowncastHelpers extends ConversionHelpers { * * @method #elementToElement * @param {Object} config Conversion configuration. - * @param {String} config.model The name of the model element to convert. + * @param {String|Object} config.model The description or a name of the model element to convert. + * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating + * the view structure. Note that the view will be reconverted if any of the listed attributes will change. + * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list + * of model child nodes changed. * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} * as parameters and returns a view container element. @@ -231,8 +294,8 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {String|Object} config.model The description or a name of the model element to convert. * @param {String} [config.model.name] The name of the model element to convert. * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating - * the view structure. Note that the view will be reconverted if any of the listed attributes will change. - * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list + * the view element. Note that the view will be reconverted if any of the listed attributes will change. + * @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list * of model child nodes changed. * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function * that takes the model element and {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi downcast From 14296936916f62b1afd0b1be97bc7e943729e8a9 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 21 Sep 2021 09:38:02 +0200 Subject: [PATCH 042/140] Travis trigger. From baf4a00e6ccffca495e5f4049220c4e48f88e776 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 21 Sep 2021 10:40:01 +0200 Subject: [PATCH 043/140] Fix jsdocs. --- .../ckeditor5-engine/src/conversion/downcasthelpers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 4f9d3774a6f..0bc92834205 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -152,8 +152,8 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {Object} config Conversion configuration. * @param {String|Object} config.model The description or a name of the model element to convert. * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating - * the view structure. Note that the view will be reconverted if any of the listed attributes will change. - * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list + * the view element. Note that the view will be reconverted if any of the listed attributes will change. + * @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list * of model child nodes changed. * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} @@ -294,8 +294,8 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {String|Object} config.model The description or a name of the model element to convert. * @param {String} [config.model.name] The name of the model element to convert. * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating - * the view element. Note that the view will be reconverted if any of the listed attributes will change. - * @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list + * the view structure. Note that the view will be reconverted if any of the listed attributes will change. + * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list * of model child nodes changed. * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function * that takes the model element and {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi downcast From c1e99cfa50e5fbd0e4a5dbca9a368404225ef5b5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 21 Sep 2021 18:47:55 +0200 Subject: [PATCH 044/140] DowncastDispatcher should not fire the same event multiple times. --- .../src/conversion/downcastdispatcher.js | 55 +++++++++++++++---- .../src/conversion/downcasthelpers.js | 4 -- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 6b38fbe9eb9..6962708fa8a 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -124,6 +124,12 @@ export default class DowncastDispatcher { * @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi} */ this._conversionApi = { dispatcher: this, ...conversionApi }; + + /** + * TODO + * @private + */ + this._firedEventsMap = new WeakMap(); } /** @@ -282,7 +288,7 @@ export default class DowncastDispatcher { // Fire a separate insert event for each node and text fragment contained in the range. for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) { - this.fire( getEventName( 'insert', data ), data, conversionApi ); + this._testAndFire( 'insert', data, conversionApi ); } } @@ -326,7 +332,7 @@ export default class DowncastDispatcher { attributeNewValue: newValue }; - this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); + this._testAndFire( `attribute:${ key }`, data, conversionApi ); } } @@ -352,7 +358,7 @@ export default class DowncastDispatcher { // Fire a separate insert event for each node and text fragment contained shallowly in the range. for ( const data of walkerValues.map( walkerValueToEventData ) ) { - this.fire( getEventName( 'insert', data ), { ...data, reconversion: true }, conversionApi ); + this._testAndFire( 'insert', { ...data, reconversion: true }, conversionApi ); } } @@ -511,6 +517,36 @@ export default class DowncastDispatcher { return consumable; } + /** + * TODO Tests passed `consumable` to check whether given event can be fired and if so, fires it. + * + * @private + * @fires insert + * @fires attribute + * @param {String} type Event type. + * @param {Object} data Event data. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. + */ + _testAndFire( type, data, conversionApi ) { + const conversionFiredEvents = this._firedEventsMap.get( conversionApi ); + // TODO should not use private method of ModelConsumable + const key = data.item.is( '$textProxy' ) ? conversionApi.consumable._getSymbolForTextProxy( data.item ) : data.item; + const itemFiredEvents = conversionFiredEvents.get( key ); + const eventName = getEventName( type, data ); + + if ( !itemFiredEvents ) { + conversionFiredEvents.set( data.item, new Set( [ eventName ] ) ); + } else { + if ( !itemFiredEvents.has( eventName ) ) { + itemFiredEvents.add( eventName ); + } else { + return; + } + } + + this.fire( eventName, data, conversionApi ); + } + /** * TODO * @@ -523,14 +559,11 @@ export default class DowncastDispatcher { }; for ( const key of data.item.getAttributeKeys() ) { - // Do not fire attribute events for just added nodes that consumed attributes. - if ( conversionApi.consumable.test( data.item, `attribute:${ key }` ) ) { - data.attributeKey = key; - data.attributeOldValue = null; - data.attributeNewValue = data.item.getAttribute( key ); + data.attributeKey = key; + data.attributeOldValue = null; + data.attributeNewValue = data.item.getAttribute( key ); - this.fire( getEventName( `attribute:${ key }`, data ), data, conversionApi ); - } + this._testAndFire( `attribute:${ key }`, data, conversionApi ); } } @@ -555,6 +588,8 @@ export default class DowncastDispatcher { convertAttributes: modelItem => this._fireAddAttributes( modelItem, conversionApi ) }; + this._firedEventsMap.set( conversionApi, new WeakMap() ); + return conversionApi; } diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 26d87fb17cd..bfe52023f02 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -690,10 +690,6 @@ export function insertText() { */ export function insertAttributesAndChildren() { return ( evt, data, conversionApi ) => { - if ( data.reconversion ) { - return; - } - conversionApi.convertAttributes( data.item ); if ( data.item.is( 'element' ) && !data.item.isEmpty ) { From 8ca2973c6191ca870100ea5392338ad50655181b Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 22 Sep 2021 08:53:41 +0200 Subject: [PATCH 045/140] Improve elementToElement helper jsdocs. --- .../src/conversion/downcasthelpers.js | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 0bc92834205..c8a15c8a5f9 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -61,19 +61,13 @@ export default class DowncastHelpers extends ConversionHelpers { * } * } ); * - * editor.conversion.for( 'downcast' ).elementToElement( { - * model: 'heading', - * view: ( modelElement, conversionApi ) => { - * const { writer } = conversionApi; - * - * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ); - * } - * } ); + * The element-to-element conversion supports the reconversion mechanism. It can be enabled by using either `attributes` or `children` + * props on a model description. Couple examples below. * - * In order to reconvert parent element if any of its direct children have been added or removed use `children` property on a `model` + * In order to reconvert element if any of its direct children have been added or removed use `children` property on a `model` * description. For example, model: * - * Some text. + * Some text. * * will be converted into this sturcture in the view: * @@ -83,20 +77,23 @@ export default class DowncastHelpers extends ConversionHelpers { * * But if more items inserted in the model: * - * Some text.Other item. + * + * Some text. + * Other item. + * * - * it will be converted into this sturcture in the view (note the parent element `data-type` change): + * it will be converted into this sturcture in the view (note the element `data-type` change): * *
*

Some text.

*

Other item.

*
* - * Such a converter would look like this: + * Such a converter would look like this (note that `paragraph` elements are converted separately): * * editor.conversion.for( 'downcast' ).elementToElement( { * model: { - * name: 'items', + * name: 'box', * children: true * }, * view: ( modelElement, conversionApi ) => { @@ -110,35 +107,31 @@ export default class DowncastHelpers extends ConversionHelpers { * } * } ); * - * In order to reconvert parent element if any of its attributes have been updated use `attributes` property on a `model` + * In order to reconvert element if any of its attributes have been updated use `attributes` property on a `model` * description. For example, model: * - * Some text. + * Some text. * * will be converted into this sturcture in the view: * - *
- *

Some text.

- *
+ *

Some text.

* - * But if `items` element type attribute has been updated to `advanced` for example, then - * it will be converted into this sturcture in the view (note the parent element `data-type` change): + * But if `heading` element `level`` attribute has been updated to `3` for example, then + * it will be converted into this sturcture in the view: * - *
- *

Some text.

- *
+ *

Some text.

* * Such a converter would look like this: * * editor.conversion.for( 'downcast' ).elementToElement( { * model: { - * name: 'items', - * attributes: [ 'type' ] + * name: 'heading', + * attributes: [ 'level' ] * }, * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; * - * return writer.createContainerElement( 'div', { class: 'items', 'data-type': modelElement.getAttribute( 'type' ) } ); + * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) ); * } * } ); * @@ -151,7 +144,7 @@ export default class DowncastHelpers extends ConversionHelpers { * @method #elementToElement * @param {Object} config Conversion configuration. * @param {String|Object} config.model The description or a name of the model element to convert. - * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating + * @param {String|Array.} [config.model.attributes] The list of attribute names that should be consumed while creating * the view element. Note that the view will be reconverted if any of the listed attributes will change. * @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list * of model child nodes changed. @@ -293,7 +286,7 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {Object} config Conversion configuration. * @param {String|Object} config.model The description or a name of the model element to convert. * @param {String} [config.model.name] The name of the model element to convert. - * @param {Array.} [config.model.attributes] The list of attribute names that should be consumed while creating + * @param {String|Array.} [config.model.attributes] The list of attribute names that should be consumed while creating * the view structure. Note that the view will be reconverted if any of the listed attributes will change. * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list * of model child nodes changed. From 1ce3d44f011cd848ae9e9a1579e4a51d35973189 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 22 Sep 2021 09:18:35 +0200 Subject: [PATCH 046/140] Default consumer function for insertElement. More updates to jsdoc. --- packages/ckeditor5-engine/src/conversion/downcasthelpers.js | 6 +++++- packages/ckeditor5-engine/src/dev-utils/model.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index c8a15c8a5f9..426bcefb246 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1008,9 +1008,10 @@ export function wrap( elementCreator ) { * * @protected * @param {Function} elementCreator Function returning a view element, which will be inserted. + * @param {Function} consumer Function defining element consumption proccess. By default this function just consume passed item insertion. * @returns {Function} Insert element event converter. */ -export function insertElement( elementCreator, consumer ) { +export function insertElement( elementCreator, consumer = ( node, consumable ) => consumable.consume( node, 'insert' ) ) { return ( evt, data, conversionApi ) => { const viewElement = elementCreator( data.item, conversionApi ); @@ -1616,6 +1617,9 @@ function removeHighlight( highlightDescriptor ) { // // @param {Object} config Conversion configuration. // @param {String} config.model +// @param {String|Object} config.model The description or a name of the model element to convert. +// @param {String|Array.} [config.model.attributes] List of attributes triggering element reconversion. +// @param {Boolean} [config.model.children] Should reconvert element if the list of model child nodes changed. // @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view // @returns {Function} Conversion helper. function downcastElementToElement( config ) { diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js index caa7689a733..91f076c190c 100644 --- a/packages/ckeditor5-engine/src/dev-utils/model.js +++ b/packages/ckeditor5-engine/src/dev-utils/model.js @@ -250,7 +250,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu const attributes = convertAttributes( modelItem.getAttributes(), stringifyAttributeValue ); return new ViewContainerElement( viewDocument, modelItem.name, attributes ); - }, ( node, consumable ) => consumable.consume( node, 'insert' ) ) ); + } ) ); downcastDispatcher.on( 'selection', convertRangeSelection() ); downcastDispatcher.on( 'selection', convertCollapsedSelection() ); From d61f30692d10c7407f8ce3c4f11d858a6ea1c3e5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 15:14:13 +0200 Subject: [PATCH 047/140] Fixed typo. --- packages/ckeditor5-engine/src/conversion/downcastdispatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index b3aee26998c..2b74cbe0417 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -135,7 +135,7 @@ export default class DowncastDispatcher { * @fires attribute * @fires addMarker * @fires removeMarker - * @fires reducedChanges + * @fires reduceChanges * @param {module:engine/model/differ~Differ} differ The differ object with buffered changes. * @param {module:engine/model/markercollection~MarkerCollection} markers Markers related to the model fragment to convert. * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. From f210c3adec01b7f79cec535f997956b003b61331 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 15:15:39 +0200 Subject: [PATCH 048/140] Review fixes. --- .../src/conversion/downcasthelpers.js | 32 +++++++++---------- .../tests/manual/elementreconversion.js | 7 +--- .../tests/manual/elementreconversion.md | 10 +++--- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 426bcefb246..b484fbe4fbe 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -67,11 +67,13 @@ export default class DowncastHelpers extends ConversionHelpers { * In order to reconvert element if any of its direct children have been added or removed use `children` property on a `model` * description. For example, model: * - * Some text. + * + * Some text. + * * - * will be converted into this sturcture in the view: + * will be converted into this structure in the view: * - *
+ *
*

Some text.

*
* @@ -82,9 +84,9 @@ export default class DowncastHelpers extends ConversionHelpers { * Other item. * * - * it will be converted into this sturcture in the view (note the element `data-type` change): + * it will be converted into this structure in the view (note the element `data-type` change): * - *
+ *
*

Some text.

*

Other item.

*
@@ -99,11 +101,10 @@ export default class DowncastHelpers extends ConversionHelpers { * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; * - * if ( modelElement.childCount === 1 ) {} - * return writer.createContainerElement( 'div', { class: 'items', 'data-type': 'single' } ); - * } - * - * return writer.createContainerElement( 'div', { class: 'items', 'data-type': 'multiple' } ); + * return writer.createContainerElement( 'div', { + * class: 'box', + * 'data-type': modelElement.childCount == 1 ? 'single' : 'multiple' + * } ); * } * } ); * @@ -112,12 +113,12 @@ export default class DowncastHelpers extends ConversionHelpers { * * Some text. * - * will be converted into this sturcture in the view: + * will be converted into this structure in the view: * *

Some text.

* - * But if `heading` element `level`` attribute has been updated to `3` for example, then - * it will be converted into this sturcture in the view: + * But if `heading` element `level` attribute has been updated to `3` for example, then + * it will be converted into this structure in the view: * *

Some text.

* @@ -126,7 +127,7 @@ export default class DowncastHelpers extends ConversionHelpers { * editor.conversion.for( 'downcast' ).elementToElement( { * model: { * name: 'heading', - * attributes: [ 'level' ] + * attributes: 'level' * }, * view: ( modelElement, conversionApi ) => { * const { writer } = conversionApi; @@ -1008,7 +1009,7 @@ export function wrap( elementCreator ) { * * @protected * @param {Function} elementCreator Function returning a view element, which will be inserted. - * @param {Function} consumer Function defining element consumption proccess. By default this function just consume passed item insertion. + * @param {Function} [consumer] Function defining element consumption process. By default this function just consume passed item insertion. * @returns {Function} Insert element event converter. */ export function insertElement( elementCreator, consumer = ( node, consumable ) => consumable.consume( node, 'insert' ) ) { @@ -1616,7 +1617,6 @@ function removeHighlight( highlightDescriptor ) { // See {@link ~DowncastHelpers#elementToElement `.elementToElement()` downcast helper} for examples and config params description. // // @param {Object} config Conversion configuration. -// @param {String} config.model // @param {String|Object} config.model The description or a name of the model element to convert. // @param {String|Array.} [config.model.attributes] List of attributes triggering element reconversion. // @param {Boolean} [config.model.children] Should reconvert element if the list of model child nodes changed. diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/elementreconversion.js index 5c7adabab9c..ccdffb131d4 100644 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.js +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.js @@ -64,11 +64,6 @@ function Items( editor ) { } } ); - editor.conversion.for( 'downcast' ).elementToElement( { - model: 'item', - view: { name: 'div', classes: 'item' } - } ); - editor.conversion.for( 'upcast' ).elementToElement( { view: { name: 'div', classes: 'items' }, model: ( viewElement, { writer } ) => { @@ -78,7 +73,7 @@ function Items( editor ) { } } ); - editor.conversion.for( 'upcast' ).elementToElement( { + editor.conversion.elementToElement( { view: { name: 'div', classes: 'item' }, model: 'item' } ); diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.md b/packages/ckeditor5-engine/tests/manual/elementreconversion.md index 0e7be98c661..c707aec6e2e 100644 --- a/packages/ckeditor5-engine/tests/manual/elementreconversion.md +++ b/packages/ckeditor5-engine/tests/manual/elementreconversion.md @@ -1,4 +1,4 @@ -# Parent element reconversion +# Element to element reconversion The editor should be loaded with `items` element that contains one `item` element in which user can edit content. @@ -6,9 +6,9 @@ Adding new items (by pressing Enter key) or removing (by pressing backspace) sho ### In threshold mode -Update parent `items` view element's `data-amount` attribute and change the list background color accordingly. +Update `items` view element's `data-amount` attribute and change the list background color accordingly. -List of threesholds: +List of thresholds: ``` | Items | Amount | Color | @@ -23,13 +23,13 @@ List of threesholds: ### In HSL mode -Update parent `items` view element's inline style background color to hsl value with hue shifted by 5 every time an `item` is addded or removed. +Update `items` view element's inline style background color to hsl value with hue shifted by 5 every time an `item` is added or removed. ### In none mode Remove background color and update nothing. -## Reconversion couter +## Reconversion counter In every mode you should be able to inspect number of times each item has been inserted. From bc3501ddc5dd265e8168e2ffc670713c16896838 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 15:18:13 +0200 Subject: [PATCH 049/140] Manual tests renaming. --- .../{elementreconversion.html => element-reconversion.html} | 0 .../manual/{elementreconversion.js => element-reconversion.js} | 0 .../manual/{elementreconversion.md => element-reconversion.md} | 0 .../tests/manual/{slotconversion.html => slot-conversion.html} | 0 .../tests/manual/{slotconversion.js => slot-conversion.js} | 0 .../tests/manual/{slotconversion.md => slot-conversion.md} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename packages/ckeditor5-engine/tests/manual/{elementreconversion.html => element-reconversion.html} (100%) rename packages/ckeditor5-engine/tests/manual/{elementreconversion.js => element-reconversion.js} (100%) rename packages/ckeditor5-engine/tests/manual/{elementreconversion.md => element-reconversion.md} (100%) rename packages/ckeditor5-engine/tests/manual/{slotconversion.html => slot-conversion.html} (100%) rename packages/ckeditor5-engine/tests/manual/{slotconversion.js => slot-conversion.js} (100%) rename packages/ckeditor5-engine/tests/manual/{slotconversion.md => slot-conversion.md} (100%) diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.html b/packages/ckeditor5-engine/tests/manual/element-reconversion.html similarity index 100% rename from packages/ckeditor5-engine/tests/manual/elementreconversion.html rename to packages/ckeditor5-engine/tests/manual/element-reconversion.html diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.js b/packages/ckeditor5-engine/tests/manual/element-reconversion.js similarity index 100% rename from packages/ckeditor5-engine/tests/manual/elementreconversion.js rename to packages/ckeditor5-engine/tests/manual/element-reconversion.js diff --git a/packages/ckeditor5-engine/tests/manual/elementreconversion.md b/packages/ckeditor5-engine/tests/manual/element-reconversion.md similarity index 100% rename from packages/ckeditor5-engine/tests/manual/elementreconversion.md rename to packages/ckeditor5-engine/tests/manual/element-reconversion.md diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.html b/packages/ckeditor5-engine/tests/manual/slot-conversion.html similarity index 100% rename from packages/ckeditor5-engine/tests/manual/slotconversion.html rename to packages/ckeditor5-engine/tests/manual/slot-conversion.html diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.js b/packages/ckeditor5-engine/tests/manual/slot-conversion.js similarity index 100% rename from packages/ckeditor5-engine/tests/manual/slotconversion.js rename to packages/ckeditor5-engine/tests/manual/slot-conversion.js diff --git a/packages/ckeditor5-engine/tests/manual/slotconversion.md b/packages/ckeditor5-engine/tests/manual/slot-conversion.md similarity index 100% rename from packages/ckeditor5-engine/tests/manual/slotconversion.md rename to packages/ckeditor5-engine/tests/manual/slot-conversion.md From a53c12b1a1c8df2c6d12e6e2b6eaf940206f1695 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 15:28:32 +0200 Subject: [PATCH 050/140] Fix typo. --- packages/ckeditor5-engine/src/conversion/downcasthelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 06712139716..d133a629a0c 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -177,7 +177,7 @@ export default class DowncastHelpers extends ConversionHelpers { * * Some text. * - * into this sturcture in the view: + * into this structure in the view: * *
*

Some text.

From dbfe554981df0abdaf747fff58f7b22f3e9b8354 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 15:52:12 +0200 Subject: [PATCH 051/140] Properly consuming. --- .../src/conversion/downcastdispatcher.js | 4 +- .../src/conversion/downcasthelpers.js | 39 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 2933bec8f14..80613b15c45 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -552,7 +552,7 @@ export default class DowncastDispatcher { * * @private */ - _fireAddAttributes( item, conversionApi ) { + _testAndFireAddAttributes( item, conversionApi ) { const data = { item, range: Range._createOn( item ) @@ -585,7 +585,7 @@ export default class DowncastDispatcher { // TODO docs for those methods in DowncastConversionApi convertItem: modelItem => this._convertInsert( Range._createOn( modelItem ), conversionApi ), convertChildren: modelItem => this._convertInsert( Range._createIn( modelItem ), conversionApi, { doNotAddConsumables: true } ), - convertAttributes: modelItem => this._fireAddAttributes( modelItem, conversionApi ) + convertAttributes: modelItem => this._testAndFireAddAttributes( modelItem, conversionApi ) }; this._firedEventsMap.set( conversionApi, new WeakMap() ); diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index d133a629a0c..dc75cc10a58 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -977,9 +977,7 @@ export function wrap( elementCreator ) { return; } - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } + conversionApi.consumable.consume( data.item, evt.name ); const viewWriter = conversionApi.writer; const viewSelection = viewWriter.document.selection; @@ -1026,12 +1024,13 @@ export function wrap( elementCreator ) { * * @protected * @param {Function} elementCreator Function returning a view element, which will be inserted. - * @param {Function} [consumer] Function defining element consumption process. By default this function just consume passed item insertion. + * @param {module:engine/conversion/downcasthelpers~ConsumerFunction} [consumer] Function defining element consumption process. + * By default this function just consume passed item insertion. * @returns {Function} Insert element event converter. */ -export function insertElement( elementCreator, consumer = ( node, consumable ) => consumable.consume( node, 'insert' ) ) { +export function insertElement( elementCreator, consumer = defaultConsumer ) { return ( evt, data, conversionApi ) => { - if ( !conversionApi.consumable.test( data.item, evt.name ) ) { + if ( !consumer( data.item, conversionApi.consumable, { preflight: true } ) ) { return; } @@ -1042,9 +1041,7 @@ export function insertElement( elementCreator, consumer = ( node, consumable ) = } // Consume an element insertion and all present attributes that are specified as a reconversion triggers. - if ( !consumer( data.item, conversionApi.consumable ) ) { - return; - } + consumer( data.item, conversionApi.consumable ); const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); @@ -1092,9 +1089,7 @@ export function insertStructure( elementCreator, consumer ) { validateSlotsChildren( data.item, slotsMap, conversionApi ); // Consume an element insertion and all present attributes that are specified as a reconversion triggers. - if ( !consumer( data.item, conversionApi.consumable ) ) { - return; - } + consumer( data.item, conversionApi.consumable ); const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); @@ -1377,9 +1372,7 @@ function changeAttribute( attributeCreator ) { return; } - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } + conversionApi.consumable.consume( data.item, evt.name ); const viewElement = conversionApi.mapper.toViewElement( data.item ); const viewWriter = conversionApi.writer; @@ -2240,6 +2233,20 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { } } +// The default consumer for insert events. +// @param {module:engine/model/item~Item} item Model item. +// @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The model consumable. +// @param {Object} [options] +// @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed. +// @returns {Boolean} +function defaultConsumer( item, consumable, { preflight } = {} ) { + if ( preflight ) { + return consumable.test( item, 'insert' ); + } else { + return consumable.consume( item, 'insert' ); + } +} + /** * An object describing how the marker highlight should be represented in the view. * @@ -2349,6 +2356,8 @@ function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { * @param {module:engine/model/element~Element} element The model element to be converted to the view structure. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The `ModelConsumable` same as in * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#consumable `DowncastConversionApi.consumable`}. + * @param {Object} [options] + * @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed. * @returns {Boolean} `true` if all consumable values were available and were consumed, `false` otherwise. * * @see module:engine/conversion/downcasthelpers~insertStructure From 690e0168895d59052b9e22b5d86df6ce1645ed94 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 16:54:56 +0200 Subject: [PATCH 052/140] Updated tests. --- .../src/conversion/downcasthelpers.js | 4 +- .../tests/conversion/downcastdispatcher.js | 78 +++++++++++++------ .../tests/conversion/mapper.js | 15 ++++ 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index dc75cc10a58..fd91cf68820 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -753,7 +753,9 @@ export function insertAttributesAndChildren() { return ( evt, data, conversionApi ) => { conversionApi.convertAttributes( data.item ); - if ( data.item.is( 'element' ) && !data.item.isEmpty ) { + // Start converting children of the current item. + // In case of reconversion children were already re-inserted or converted separately. + if ( !data.reconversion && data.item.is( 'element' ) && !data.item.isEmpty ) { conversionApi.convertChildren( data.item ); } }; diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index e059e3a3372..c8efb968ddb 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -356,31 +356,48 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); - it( 'should not fire events for already consumed parts of model', () => { + it( 'should not fire same events multiple times', () => { root._appendChild( [ new ModelElement( 'imageBlock', { src: 'foo.jpg', title: 'bar', bold: true }, [ new ModelElement( 'caption', {}, new ModelText( 'title' ) ) ] ) ] ); - sinon.spy( dispatcher, 'fire' ); + const loggedEvents = []; + + dispatcher.on( 'insert', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + } ); + + dispatcher.on( 'attribute', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + } ); dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item.getChild( 0 ), 'insert' ); - conversionApi.consumable.consume( data.item, 'attribute:bold' ); + conversionApi.convertAttributes( data.item ); + conversionApi.convertChildren( data.item ); } ); const range = model.createRangeIn( root ); dispatcher.convert( range, [] ); - expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'attribute:title:imageBlock' ) ).to.be.true; - - expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; - expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; - expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.false; + expect( loggedEvents ).to.deep.equal( [ + 'insert:imageBlock:0:1', + 'attribute:src:foo.jpg:imageBlock:0:1', + 'attribute:title:bar:imageBlock:0:1', + 'attribute:bold:true:imageBlock:0:1', + 'insert:caption:0,0:0,1', + 'insert:$text:title:0,0,0:0,0,5' + ] ); expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; @@ -515,18 +532,34 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); - it( 'should not fire events for already consumed parts of model', () => { + it( 'should not fire same events multiple times', () => { root._appendChild( [ new ModelElement( 'imageBlock', { src: 'foo.jpg', title: 'bar', bold: true }, [ new ModelElement( 'caption', {}, new ModelText( 'title' ) ) ] ) ] ); - sinon.spy( dispatcher, 'fire' ); + const loggedEvents = []; + + dispatcher.on( 'insert', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + } ); + + dispatcher.on( 'attribute', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + } ); dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item.getChild( 0 ), 'insert' ); - conversionApi.consumable.consume( data.item, 'attribute:bold' ); + conversionApi.convertAttributes( data.item ); + conversionApi.convertChildren( data.item ); } ); const range = model.createRangeIn( root ); @@ -535,13 +568,14 @@ describe( 'DowncastDispatcher', () => { dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); } ); - expect( dispatcher.fire.calledWith( 'insert:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'attribute:src:imageBlock' ) ).to.be.true; - expect( dispatcher.fire.calledWith( 'attribute:title:imageBlock' ) ).to.be.true; - - expect( dispatcher.fire.calledWith( 'attribute:bold:imageBlock' ) ).to.be.false; - expect( dispatcher.fire.calledWith( 'insert:caption' ) ).to.be.false; - expect( dispatcher.fire.calledWith( 'insert:$text' ) ).to.be.false; + expect( loggedEvents ).to.deep.equal( [ + 'insert:imageBlock:0:1', + 'attribute:src:foo.jpg:imageBlock:0:1', + 'attribute:title:bar:imageBlock:0:1', + 'attribute:bold:true:imageBlock:0:1', + 'insert:caption:0,0:0,1', + 'insert:$text:title:0,0,0:0,0,5' + ] ); expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; diff --git a/packages/ckeditor5-engine/tests/conversion/mapper.js b/packages/ckeditor5-engine/tests/conversion/mapper.js index dfa208ed542..8813e9df5e8 100644 --- a/packages/ckeditor5-engine/tests/conversion/mapper.js +++ b/packages/ckeditor5-engine/tests/conversion/mapper.js @@ -20,6 +20,8 @@ import ViewRange from '../../src/view/range'; import ViewDocumentFragment from '../../src/view/documentfragment'; import { StylesProcessor } from '../../src/view/stylesmap'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; + describe( 'Mapper', () => { let viewDocument; @@ -433,6 +435,19 @@ describe( 'Mapper', () => { expect( result ).to.equal( stub ); } ); + it( 'should throw an error on missing position parent view element', () => { + // The foo element was not downcasted to view. + const modelElement = new ModelElement( 'foo' ); + + modelDiv._appendChild( modelElement ); + + const modelPosition = new ModelPosition( modelElement, [ 0 ] ); + + expect( () => { + mapper.toViewPosition( modelPosition ); + } ).to.throw( CKEditorError, 'mapping-view-position-parent-not-found' ); + } ); + // Default algorithm tests. it( 'should transform modelDiv 0', () => createToViewTest( modelDiv, 0, viewTextX, 0 ) ); it( 'should transform modelDiv 1', () => createToViewTest( modelDiv, 1, viewTextX, 1 ) ); From 28751092ee53067f67276e64a1f26704b4a8025a Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 17:55:11 +0200 Subject: [PATCH 053/140] Added JSDocs. --- .../src/conversion/downcastdispatcher.js | 61 ++++++++++------ .../src/conversion/downcasthelpers.js | 2 - .../ckeditor5-engine/src/conversion/mapper.js | 3 +- .../src/conversion/modelconsumable.js | 2 +- .../tests/conversion/downcastdispatcher.js | 72 +++++++++++++++++++ 5 files changed, 114 insertions(+), 26 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 80613b15c45..1dab1b427ae 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -126,8 +126,10 @@ export default class DowncastDispatcher { this._conversionApi = { dispatcher: this, ...conversionApi }; /** - * TODO + * A map of already fired events for a given `ModelConsumable`. + * * @private + * @member {WeakMap.} */ this._firedEventsMap = new WeakMap(); } @@ -278,7 +280,9 @@ export default class DowncastDispatcher { * @fires attribute * @param {module:engine/model/range~Range} range The inserted range. * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. - * @param {Object} [options] TODO + * @param {Object} [options] + * @param {Boolean} [options.doNotAddConsumables=false] Whether the ModelConsumable should not get populated + * for items in the provided range. */ _convertInsert( range, conversionApi, options = {} ) { if ( !options.doNotAddConsumables ) { @@ -518,7 +522,7 @@ export default class DowncastDispatcher { } /** - * TODO Tests passed `consumable` to check whether given event can be fired and if so, fires it. + * Tests whether given event wasn't already fired and if so, fires it. * * @private * @fires insert @@ -528,29 +532,29 @@ export default class DowncastDispatcher { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _testAndFire( type, data, conversionApi ) { - const conversionFiredEvents = this._firedEventsMap.get( conversionApi ); - // TODO should not use private method of ModelConsumable - const key = data.item.is( '$textProxy' ) ? conversionApi.consumable._getSymbolForTextProxy( data.item ) : data.item; - const itemFiredEvents = conversionFiredEvents.get( key ); const eventName = getEventName( type, data ); + const itemKey = data.item.is( '$textProxy' ) ? conversionApi.consumable._getSymbolForTextProxy( data.item ) : data.item; + + const conversionFiredEvents = this._firedEventsMap.get( conversionApi ); + const itemFiredEvents = conversionFiredEvents.get( itemKey ); if ( !itemFiredEvents ) { - conversionFiredEvents.set( data.item, new Set( [ eventName ] ) ); + conversionFiredEvents.set( itemKey, new Set( [ eventName ] ) ); + } else if ( !itemFiredEvents.has( eventName ) ) { + itemFiredEvents.add( eventName ); } else { - if ( !itemFiredEvents.has( eventName ) ) { - itemFiredEvents.add( eventName ); - } else { - return; - } + return; } this.fire( eventName, data, conversionApi ); } /** - * TODO + * Fires not already fired events for setting attributes on just inserted item. * * @private + * @param {module:engine/model/item~Item} item The model item to convert attributes for. + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object. */ _testAndFireAddAttributes( item, conversionApi ) { const data = { @@ -582,13 +586,12 @@ export default class DowncastDispatcher { consumable: new Consumable(), writer, options, - // TODO docs for those methods in DowncastConversionApi - convertItem: modelItem => this._convertInsert( Range._createOn( modelItem ), conversionApi ), - convertChildren: modelItem => this._convertInsert( Range._createIn( modelItem ), conversionApi, { doNotAddConsumables: true } ), - convertAttributes: modelItem => this._testAndFireAddAttributes( modelItem, conversionApi ) + convertItem: item => this._convertInsert( Range._createOn( item ), conversionApi ), + convertChildren: element => this._convertInsert( Range._createIn( element ), conversionApi, { doNotAddConsumables: true } ), + convertAttributes: item => this._testAndFireAddAttributes( item, conversionApi ) }; - this._firedEventsMap.set( conversionApi, new WeakMap() ); + this._firedEventsMap.set( conversionApi, new Map() ); return conversionApi; } @@ -811,11 +814,25 @@ function walkerValueToEventData( value ) { */ /** - * Triggers conversion of a specified range. + * Triggers conversion of a specified item. * This conversion is triggered within (as a separate process of) the parent conversion. * - * @method #convertInsert - * @param {module:engine/model/range~Range} range The range to trigger nested insert conversion on. + * @method #convertItem + * @param {module:engine/model/item~Item} item The model item to trigger nested insert conversion on. + */ + +/** + * Triggers conversion of children of a specified element. + * + * @method #convertChildren + * @param {module:engine/model/element~Element} element The model element to trigger children insert conversion on. + */ + +/** + * Triggers conversion of attributes of a specified item. + * + * @method #convertChildren + * @param {module:engine/model/item~Item} item The model item to trigger attribute conversion on. */ /** diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index fd91cf68820..29d721ef377 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2042,8 +2042,6 @@ function createChangeReducer( model ) { // For insert or remove use parent element because we need to check if it's added/removed child. const node = change.position ? change.position.parent : change.range.start.nodeAfter; - // TODO Node might not be here if it's change of an attribute in the middle of some text. - // undoediting-integration.js line 323 fails without this check if ( !node || !shouldReplace( node, change ) ) { reducedChanges.push( change ); diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 6112e0726ee..00ff4c96488 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -116,7 +116,8 @@ export default class Mapper { if ( !viewContainer ) { /** - * TODO + * Can not map given model position to the view position because position parent view element is not present + * in the document tree. Make sure that parent element is converted to the view. * * @error mapping-view-position-parent-not-found */ diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index 20b0175b694..7d19b768d99 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -252,7 +252,7 @@ export default class ModelConsumable { * * Used internally to correctly consume `TextProxy` instances. * - * @private + * @protected * @param {module:engine/model/textproxy~TextProxy} textProxy `TextProxy` instance to get a symbol for. * @returns {Symbol} Symbol representing all equal instances of `TextProxy`. */ diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index c8efb968ddb..f957f1cf3b8 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -532,6 +532,73 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); + it( 'should fire events only for shallow range', () => { + // Set new dispatcher without convert attributes and children handler. + dispatcher = new DowncastDispatcher( { mapper, apiObj } ); + + root._appendChild( [ + new ModelText( 'foo', { bold: true } ), + new ModelElement( 'imageBlock', null, new ModelElement( 'caption' ) ), + new ModelText( 'bar' ), + new ModelElement( 'paragraph', { class: 'nice' }, new ModelText( 'xx', { italic: true } ) ) + ] ); + + const range = model.createRangeIn( root ); + const loggedEvents = []; + let consumable; + + // We will check everything connected with insert event: + dispatcher.on( 'insert', ( evt, data, conversionApi ) => { + // Check if the item is correct. + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + // Check if the range is correct. + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + // Check if the event name is correct. + expect( evt.name ).to.equal( 'insert:' + ( data.item.name || '$text' ) ); + // Check if model consumable is correct. + expect( conversionApi.consumable.consume( data.item, 'insert' ) ).to.be.true; + expect( data ).to.not.have.property( 'reconversion' ); + + consumable = conversionApi.consumable; + } ); + + // Same here. + dispatcher.on( 'attribute', ( evt, data, conversionApi ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + + expect( evt.name ).to.equal( 'attribute:' + key + ':' + ( data.item.name || '$text' ) ); + expect( conversionApi.consumable.consume( data.item, 'attribute:' + key ) ).to.be.true; + } ); + + view.change( writer => { + dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); + } ); + + // Check the data passed to called events and the order of them. + expect( loggedEvents ).to.deep.equal( [ + 'insert:$text:foo:0:3', + 'insert:imageBlock:3:4', + 'insert:$text:bar:4:7', + 'insert:paragraph:7:8' + ] ); + + // Consumable should be populated with all the events (even those nested). + expect( consumable.test( root.getChild( 1 ).getChild( 0 ), 'insert' ) ).to.be.true; + expect( consumable.test( root.getChild( 3 ), 'attribute:class:paragraph' ) ).to.be.true; + expect( consumable.test( root.getChild( 1 ), 'insert' ) ).to.be.false; + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); + it( 'should not fire same events multiple times', () => { root._appendChild( [ new ModelElement( 'imageBlock', { src: 'foo.jpg', title: 'bar', bold: true }, [ @@ -562,6 +629,11 @@ describe( 'DowncastDispatcher', () => { conversionApi.convertChildren( data.item ); } ); + dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.convertAttributes( data.item ); + conversionApi.convertChildren( data.item ); + } ); + const range = model.createRangeIn( root ); view.change( writer => { From 2b1a83b003b1b025b4aaf5f29ed3cd6f5c190aae Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 17:58:12 +0200 Subject: [PATCH 054/140] Added JSDocs. --- packages/ckeditor5-engine/src/conversion/downcasthelpers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 29d721ef377..e02c5b8a700 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -747,7 +747,9 @@ export function insertText() { } /** - * TODO + * Function factory that creates a default downcast converter for triggering attributes and children conversion. + * + * @returns {Function} The converter. */ export function insertAttributesAndChildren() { return ( evt, data, conversionApi ) => { From 310e2821b79cc6c3298dcad51f6239b9374faa62 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 18:42:00 +0200 Subject: [PATCH 055/140] Consuming list attribute events. --- packages/ckeditor5-list/src/converters.js | 9 +++------ packages/ckeditor5-list/src/liststyleediting.js | 4 ++++ packages/ckeditor5-list/src/todolistconverters.js | 4 ++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-list/src/converters.js b/packages/ckeditor5-list/src/converters.js index dab6e4e3204..31f38ff239f 100644 --- a/packages/ckeditor5-list/src/converters.js +++ b/packages/ckeditor5-list/src/converters.js @@ -109,7 +109,7 @@ export function modelViewRemove( model ) { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface. */ export function modelViewChangeType( evt, data, conversionApi ) { - if ( !conversionApi.consumable.consume( data.item, 'attribute:listType' ) ) { + if ( !conversionApi.consumable.test( data.item, evt.name ) ) { return; } @@ -138,6 +138,8 @@ export function modelViewChangeType( evt, data, conversionApi ) { * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface. */ export function modelViewMergeAfterChangeType( evt, data, conversionApi ) { + conversionApi.consumable.consume( data.item, evt.name ); + const viewItem = conversionApi.mapper.toViewElement( data.item ); const viewList = viewItem.parent; const viewWriter = conversionApi.writer; @@ -145,11 +147,6 @@ export function modelViewMergeAfterChangeType( evt, data, conversionApi ) { // Merge the changed view list with other lists, if possible. mergeViewLists( viewWriter, viewList, viewList.nextSibling ); mergeViewLists( viewWriter, viewList.previousSibling, viewList ); - - // Consumable insertion of children inside the item. They are already handled by re-building the item in view. - for ( const child of data.item.getChildren() ) { - conversionApi.consumable.consume( child, 'insert' ); - } } /** diff --git a/packages/ckeditor5-list/src/liststyleediting.js b/packages/ckeditor5-list/src/liststyleediting.js index 9c1557d1634..41c4f6ce1c1 100644 --- a/packages/ckeditor5-list/src/liststyleediting.js +++ b/packages/ckeditor5-list/src/liststyleediting.js @@ -234,6 +234,10 @@ function upcastListItemStyle() { function downcastListStyleAttribute() { return dispatcher => { dispatcher.on( 'attribute:listStyle:listItem', ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + const viewWriter = conversionApi.writer; const currentElement = data.item; diff --git a/packages/ckeditor5-list/src/todolistconverters.js b/packages/ckeditor5-list/src/todolistconverters.js index ff532b34db6..127990b2ef7 100644 --- a/packages/ckeditor5-list/src/todolistconverters.js +++ b/packages/ckeditor5-list/src/todolistconverters.js @@ -183,6 +183,10 @@ export function dataViewModelCheckmarkInsertion( evt, data, conversionApi ) { */ export function modelViewChangeType( onCheckedChange, view ) { return ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + const viewItem = conversionApi.mapper.toViewElement( data.item ); const viewWriter = conversionApi.writer; From 2e040d76f6de12ce4096bab2d2cb0ea2d2bb6ea6 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 22 Sep 2021 18:55:48 +0200 Subject: [PATCH 056/140] Added tests. --- .../ckeditor5-list/tests/liststyleediting.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/ckeditor5-list/tests/liststyleediting.js b/packages/ckeditor5-list/tests/liststyleediting.js index f1038b4e09f..cf34df76742 100644 --- a/packages/ckeditor5-list/tests/liststyleediting.js +++ b/packages/ckeditor5-list/tests/liststyleediting.js @@ -259,6 +259,22 @@ describe( 'ListStyleEditing', () => { expect( listItem.hasAttribute( 'listItem' ) ).to.be.false; } ); + + it( 'should be overridable', () => { + editor.conversion.for( 'dataDowncast' ).attributeToAttribute( { model: 'listStyle', view: 'data-style' } ); + + setModelData( model, + 'Foo' + + 'Bar' + ); + + expect( editor.getData() ).to.equal( + '
    ' + + '
  • Foo
  • ' + + '
  • Bar
  • ' + + '
' + ); + } ); } ); describe( 'view to model', () => { @@ -461,6 +477,22 @@ describe( 'ListStyleEditing', () => { '' ); } ); + + it( 'should be overridable', () => { + editor.conversion.for( 'editingDowncast' ).attributeToAttribute( { model: 'listStyle', view: 'data-style' } ); + + setModelData( model, + 'Foo' + + 'Bar' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
    ' + + '
  • Foo
  • ' + + '
  • Bar
  • ' + + '
' + ); + } ); } ); } ); From fc259b537cdae58312dffba753a2bc07d2d315b9 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 23 Sep 2021 13:05:37 +0200 Subject: [PATCH 057/140] Fixed test. --- packages/ckeditor5-html-support/tests/datafilter.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-html-support/tests/datafilter.js b/packages/ckeditor5-html-support/tests/datafilter.js index beb768f186e..bf175c30281 100644 --- a/packages/ckeditor5-html-support/tests/datafilter.js +++ b/packages/ckeditor5-html-support/tests/datafilter.js @@ -336,10 +336,12 @@ describe( 'DataFilter', () => { } ); it( 'should consume htmlAttributes attribute (editing downcast)', () => { - const spy = sinon.spy(); + let consumable; editor.conversion.for( 'editingDowncast' ).add( dispatcher => { - dispatcher.on( 'attribute:htmlAttributes:htmlInput', spy ); + dispatcher.on( 'insert:htmlInput', ( evt, data, conversionApi ) => { + consumable = conversionApi.consumable; + } ); } ); dataFilter.allowElement( 'input' ); @@ -347,7 +349,7 @@ describe( 'DataFilter', () => { editor.setData( '

' ); - expect( spy.called ).to.be.false; + expect( consumable.test( model.document.getRoot().getChild( 0 ).getChild( 0 ), 'attribute:htmlAttributes' ) ).to.be.false; } ); function getObjectModelDataWithAttributes( model, options ) { From 7ff855ae95f6fbf3a2668b929ed802793b5e1fdc Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 23 Sep 2021 13:38:01 +0200 Subject: [PATCH 058/140] JSDoc fix. --- packages/ckeditor5-engine/src/conversion/downcastdispatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 1dab1b427ae..10461cd3805 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -831,7 +831,7 @@ function walkerValueToEventData( value ) { /** * Triggers conversion of attributes of a specified item. * - * @method #convertChildren + * @method #convertAttributes * @param {module:engine/model/item~Item} item The model item to trigger attribute conversion on. */ From 2aede05203bb0332d6067f3f868d173b8bb3f6c8 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 23 Sep 2021 13:40:12 +0200 Subject: [PATCH 059/140] JSDoc fix. --- packages/ckeditor5-engine/src/conversion/downcasthelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index e02c5b8a700..ca86312d2e5 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2001,7 +2001,7 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) { // @param {String} model.name The name of element. // @param {Array.} model.attributes The list of attribute names that should trigger reconversion. // @param {Boolean} [model.children] Whether the child list change should trigger reconversion. -// @returns {Boolean} +// @returns {Function} function createChangeReducerCallback( model ) { return ( node, change ) => { if ( !node.is( 'element', model.name ) ) { From baf38e555d2fcd08f06be502943bd9997d8ae6e5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 23 Sep 2021 15:13:11 +0200 Subject: [PATCH 060/140] Added tests. --- .../tests/conversion/downcastdispatcher.js | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index f957f1cf3b8..163dc61adc2 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -468,6 +468,58 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); + + it( 'should be possible to listen for the insert event with the lowest priority to get subtree converted', () => { + root._appendChild( [ + new ModelElement( 'imageBlock', { src: 'foo.jpg' }, [ + new ModelElement( 'caption', {}, new ModelText( 'title' ) ) + ] ) + ] ); + + const range = model.createRangeIn( root ); + const spyBefore = sinon.spy(); + const spyAfter = sinon.spy(); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:$text', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'attribute:src:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + spyBefore(); + + expect( conversionApi.consumable.test( data.item, 'insert' ) ).to.be.true; + expect( conversionApi.consumable.test( data.item, 'attribute:src' ) ).to.be.true; + expect( conversionApi.consumable.test( data.item.getChild( 0 ), 'insert' ) ).to.be.true; + }, { priority: 'highest' } ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + spyAfter(); + + expect( conversionApi.consumable.test( data.item, 'insert' ) ).to.be.false; + expect( conversionApi.consumable.test( data.item, 'attribute:src' ) ).to.be.false; + expect( conversionApi.consumable.test( data.item.getChild( 0 ), 'insert' ) ).to.be.false; + }, { priority: 'lowest' } ); + + dispatcher.convert( range, [] ); + + expect( spyBefore.calledOnce ).to.be.true; + expect( spyAfter.calledOnce ).to.be.true; + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); } ); describe( '_convertInsert', () => { @@ -704,6 +756,60 @@ describe( 'DowncastDispatcher', () => { 'insert:$text:title:0,0,0:0,0,5' ] ); } ); + + it( 'should be possible to listen for the insert event with the lowest priority to get subtree converted', () => { + root._appendChild( [ + new ModelElement( 'imageBlock', { src: 'foo.jpg' }, [ + new ModelElement( 'caption', {}, new ModelText( 'title' ) ) + ] ) + ] ); + + const range = model.createRangeIn( root ); + const spyBefore = sinon.spy(); + const spyAfter = sinon.spy(); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:$text', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'attribute:src:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + spyBefore(); + + expect( conversionApi.consumable.test( data.item, 'insert' ) ).to.be.true; + expect( conversionApi.consumable.test( data.item, 'attribute:src' ) ).to.be.true; + expect( conversionApi.consumable.test( data.item.getChild( 0 ), 'insert' ) ).to.be.true; + }, { priority: 'highest' } ); + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + spyAfter(); + + expect( conversionApi.consumable.test( data.item, 'insert' ) ).to.be.false; + expect( conversionApi.consumable.test( data.item, 'attribute:src' ) ).to.be.false; + expect( conversionApi.consumable.test( data.item.getChild( 0 ), 'insert' ) ).to.be.false; + }, { priority: 'lowest' } ); + + view.change( writer => { + dispatcher._convertInsert( range, dispatcher._createConversionApi( writer ) ); + } ); + + expect( spyBefore.calledOnce ).to.be.true; + expect( spyAfter.calledOnce ).to.be.true; + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); } ); describe( '_convertReinsert', () => { From 99cbd92f347076bc03d96eb7e6888b9b8ebedb78 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 23 Sep 2021 18:19:16 +0200 Subject: [PATCH 061/140] Error should be thrown if not all insert events were consumed by downcast converters. --- .../tests/pasteplaintext.js | 2 +- .../src/conversion/downcastdispatcher.js | 6 ++++ .../src/conversion/modelconsumable.js | 34 ++++++++++++++++++- packages/ckeditor5-engine/src/model/differ.js | 6 ++-- .../tests/conversion/downcastdispatcher.js | 8 +++-- .../tests/conversion/downcasthelpers.js | 4 +++ packages/ckeditor5-heading/src/title.js | 4 +++ .../tests/imagecaption/imagecaptionediting.js | 12 +++++++ 8 files changed, 69 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-clipboard/tests/pasteplaintext.js b/packages/ckeditor5-clipboard/tests/pasteplaintext.js index 39969199f41..a03d6f8bc70 100644 --- a/packages/ckeditor5-clipboard/tests/pasteplaintext.js +++ b/packages/ckeditor5-clipboard/tests/pasteplaintext.js @@ -43,7 +43,7 @@ describe( 'PastePlainText', () => { isInline: true } ); - editor.conversion.for( 'upcast' ).elementToElement( { + editor.conversion.elementToElement( { model: 'softBreak', view: 'br' } ); diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 10461cd3805..a2d4baf7486 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -187,6 +187,9 @@ export default class DowncastDispatcher { // Remove mappings for all removed view elements. conversionApi.mapper.flushDeferredBindings(); + + // Verify if all insert consumables were consumed. + conversionApi.consumable.verifyAllConsumed( [ 'insert' ] ); } /** @@ -208,6 +211,9 @@ export default class DowncastDispatcher { for ( const [ name, range ] of markers ) { this._convertMarkerAdd( name, range, conversionApi ); } + + // Verify if all insert consumables were consumed. + conversionApi.consumable.verifyAllConsumed( [ 'insert' ] ); } /** diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index 7d19b768d99..a55ae234e94 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -8,6 +8,7 @@ */ import TextProxy from '../model/textproxy'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; /** * Manages a list of consumable values for {@link module:engine/model/item~Item model items}. @@ -246,6 +247,37 @@ export default class ModelConsumable { return null; } + /** + * TODO + * + * @param {Array.} events The events group to verify. + */ + verifyAllConsumed( events ) { + const items = []; + + for ( const [ item, consumables ] of this._consumable ) { + for ( const [ event, canConsume ] of consumables ) { + const eventPrefix = event.split( ':' )[ 0 ]; + + if ( canConsume && events.includes( eventPrefix ) ) { + items.push( { + event, + item: item.name || item.description + } ); + } + } + } + + if ( items.length ) { + /** + * TODO + * + * @error conversion-model-consumable-not-consumed + */ + throw new CKEditorError( 'conversion-model-consumable-not-consumed', null, { items } ); + } + } + /** * Gets a unique symbol for passed {@link module:engine/model/textproxy~TextProxy} instance. All `TextProxy` instances that * have same parent, same start index and same end index will get the same symbol. @@ -290,7 +322,7 @@ export default class ModelConsumable { const end = textProxy.endOffset; const parent = textProxy.parent; - const symbol = Symbol( 'textProxySymbol' ); + const symbol = Symbol( '$textProxy:' + textProxy.data ); let startMap, endMap; startMap = this._textProxyRegistry.get( start ); diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 32a4ad1c18a..8fbb655629d 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -540,13 +540,13 @@ export default class Differ { this._changeCount = 0; // Cache changes. - this._cachedChangesWithGraveyard = diffSet.slice(); + this._cachedChangesWithGraveyard = diffSet; this._cachedChanges = diffSet.filter( _changesInGraveyardFilter ); if ( options.includeChangesInGraveyard ) { - return this._cachedChangesWithGraveyard; + return this._cachedChangesWithGraveyard.slice(); } else { - return this._cachedChanges; + return this._cachedChanges.slice(); } } diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 163dc61adc2..fb50c55f14c 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -365,20 +365,24 @@ describe( 'DowncastDispatcher', () => { const loggedEvents = []; - dispatcher.on( 'insert', ( evt, data ) => { + dispatcher.on( 'insert', ( evt, data, conversionApi ) => { const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; loggedEvents.push( log ); + + conversionApi.consumable.consume( data.item, evt.name ); } ); - dispatcher.on( 'attribute', ( evt, data ) => { + dispatcher.on( 'attribute', ( evt, data, conversionApi ) => { const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; const key = data.attributeKey; const value = data.attributeNewValue; const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; loggedEvents.push( log ); + + conversionApi.consumable.consume( data.item, evt.name ); } ); dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 3923d0192ca..b51dee7583c 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -126,6 +126,10 @@ describe( 'DowncastHelpers', () => { view: () => null } ); + controller.downcastDispatcher.on( 'insert:heading', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + }, { priority: 'lowest' } ); + model.change( writer => { writer.insertElement( 'heading', {}, modelRoot, 0 ); } ); diff --git a/packages/ckeditor5-heading/src/title.js b/packages/ckeditor5-heading/src/title.js index e93b926689e..529c2505088 100644 --- a/packages/ckeditor5-heading/src/title.js +++ b/packages/ckeditor5-heading/src/title.js @@ -87,6 +87,10 @@ export default class Title extends Plugin { // Conversion. editor.conversion.for( 'downcast' ).elementToElement( { model: 'title-content', view: 'h1' } ); + editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert:title', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + } ) ); + // Custom converter is used for data v -> m conversion to avoid calling post-fixer when setting data. // See https://github.com/ckeditor/ckeditor5/issues/2036. editor.data.upcastDispatcher.on( 'element:h1', dataViewModelH1Insertion, { priority: 'high' } ); diff --git a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js index 198e723923c..e06e09790fc 100644 --- a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js +++ b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js @@ -179,6 +179,12 @@ describe( 'ImageCaptionEditing', () => { } ); it( 'should not convert caption from other elements', () => { + editor.conversion.for( 'downcast' ).add( + dispatcher => dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + }, { priority: 'lowest' } ) + ); + setModelData( model, 'foo bar
' ); expect( editor.getData() ).to.equal( 'foo bar' ); @@ -203,6 +209,12 @@ describe( 'ImageCaptionEditing', () => { } ); it( 'should not convert caption from other elements', () => { + editor.conversion.for( 'downcast' ).add( + dispatcher => dispatcher.on( 'insert:caption', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, evt.name ); + }, { priority: 'lowest' } ) + ); + setModelData( model, 'foo bar' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( 'foo bar' ); } ); From 40e366c018de15d74f1ab1046a329e2ef4fb4a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Fri, 24 Sep 2021 15:01:52 +0200 Subject: [PATCH 062/140] Minor changes. --- .../src/conversion/downcastdispatcher.js | 14 +++++++------- .../src/conversion/downcasthelpers.js | 2 +- packages/ckeditor5-engine/src/conversion/mapper.js | 6 ++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 10461cd3805..ad5e5d66834 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -129,7 +129,7 @@ export default class DowncastDispatcher { * A map of already fired events for a given `ModelConsumable`. * * @private - * @member {WeakMap.} + * @member {WeakMap.} */ this._firedEventsMap = new WeakMap(); } @@ -535,13 +535,13 @@ export default class DowncastDispatcher { const eventName = getEventName( type, data ); const itemKey = data.item.is( '$textProxy' ) ? conversionApi.consumable._getSymbolForTextProxy( data.item ) : data.item; - const conversionFiredEvents = this._firedEventsMap.get( conversionApi ); - const itemFiredEvents = conversionFiredEvents.get( itemKey ); + const eventsFiredForConversion = this._firedEventsMap.get( conversionApi ); + const eventsFiredForItem = eventsFiredForConversion.get( itemKey ); - if ( !itemFiredEvents ) { - conversionFiredEvents.set( itemKey, new Set( [ eventName ] ) ); - } else if ( !itemFiredEvents.has( eventName ) ) { - itemFiredEvents.add( eventName ); + if ( !eventsFiredForItem ) { + eventsFiredForConversion.set( itemKey, new Set( [ eventName ] ) ); + } else if ( !eventsFiredForItem.has( eventName ) ) { + eventsFiredForItem.add( eventName ); } else { return; } diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index ca86312d2e5..3db1f388a77 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -2074,7 +2074,7 @@ function createChangeReducer( model ) { }; } -// Creates a function that checks if an element and it's watched attributes can be consumed and consumes them. +// Creates a function that checks if an element and its watched attributes can be consumed and consumes them. // // @param {Object} model A normalized `config.model` converter configuration. // @param {String} model.name The name of element. diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 00ff4c96488..1959fbb0459 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -116,8 +116,10 @@ export default class Mapper { if ( !viewContainer ) { /** - * Can not map given model position to the view position because position parent view element is not present - * in the document tree. Make sure that parent element is converted to the view. + * A model position could not be mapped to the view because the parent of the model position + * does not have a mapped view element (might have not been converted yet or it has no converter). + * + * Make sure that the model element is correctly converted to the view. * * @error mapping-view-position-parent-not-found */ From d0e5c88c30460e436bada9310adcd664403c18f5 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 24 Sep 2021 17:49:33 +0200 Subject: [PATCH 063/140] Added tests. --- .../src/conversion/modelconsumable.js | 2 +- .../tests/conversion/downcastdispatcher.js | 34 +++++++++- .../tests/conversion/modelconsumable.js | 63 +++++++++++++++++-- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index a55ae234e94..149d699e087 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -248,7 +248,7 @@ export default class ModelConsumable { } /** - * TODO + * Verifies if all events from specified group were consumed. * * @param {Array.} events The events group to verify. */ diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index fb50c55f14c..7b0fc337969 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -3,6 +3,8 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; + import DowncastDispatcher from '../../src/conversion/downcastdispatcher'; import Mapper from '../../src/conversion/mapper'; @@ -51,7 +53,11 @@ describe( 'DowncastDispatcher', () => { describe( 'convertChanges', () => { it( 'should call _convertInsert for insert change', () => { - sinon.stub( dispatcher, '_convertInsert' ); + let spyConsumableVerify; + + sinon.stub( dispatcher, '_convertInsert' ).callsFake( ( range, conversionApi ) => { + spyConsumableVerify = spyConsumableVerify || sinon.spy( conversionApi.consumable, 'verifyAllConsumed' ); + } ); sinon.stub( mapper, 'flushDeferredBindings' ); const position = model.createPositionFromPath( root, [ 0 ] ); @@ -69,6 +75,8 @@ describe( 'DowncastDispatcher', () => { assertConversionApi( dispatcher._convertInsert.firstCall.args[ 1 ] ); expect( mapper.flushDeferredBindings.calledOnce ).to.be.true; + expect( spyConsumableVerify.calledOnce ).to.be.true; + expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); @@ -524,6 +532,30 @@ describe( 'DowncastDispatcher', () => { expect( dispatcher._conversionApi.writer ).to.be.undefined; expect( dispatcher._conversionApi.consumable ).to.be.undefined; } ); + + it( 'should throw if not all insert events were consumed', () => { + root._appendChild( [ + new ModelElement( 'imageBlock', { src: 'foo.jpg' }, [ + new ModelElement( 'caption', {}, new ModelText( 'title' ) ) + ] ) + ] ); + + const range = model.createRangeIn( root ); + let spy; + + dispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => { + spy = sinon.spy( conversionApi.consumable, 'verifyAllConsumed' ); + } ); + + expect( () => { + dispatcher.convert( range, [] ); + } ).to.throw( CKEditorError, 'conversion-model-consumable-not-consumed' ); + + expect( spy.calledOnce ).to.be.true; + + expect( dispatcher._conversionApi.writer ).to.be.undefined; + expect( dispatcher._conversionApi.consumable ).to.be.undefined; + } ); } ); describe( '_convertInsert', () => { diff --git a/packages/ckeditor5-engine/tests/conversion/modelconsumable.js b/packages/ckeditor5-engine/tests/conversion/modelconsumable.js index 31974e927cb..bf1109e2b1f 100644 --- a/packages/ckeditor5-engine/tests/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/tests/conversion/modelconsumable.js @@ -8,6 +8,8 @@ import ModelElement from '../../src/model/element'; import ModelTextProxy from '../../src/model/textproxy'; import ModelText from '../../src/model/text'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; + describe( 'ModelConsumable', () => { let modelConsumable, modelElement; @@ -16,7 +18,7 @@ describe( 'ModelConsumable', () => { modelElement = new ModelElement( 'paragraph', null, new ModelText( 'foobar' ) ); } ); - describe( 'add', () => { + describe( 'add()', () => { it( 'should add consumable value from given element of given type', () => { modelConsumable.add( modelElement, 'type' ); @@ -77,7 +79,7 @@ describe( 'ModelConsumable', () => { } ); } ); - describe( 'consume', () => { + describe( 'consume()', () => { it( 'should remove consumable value of given type for given element and return true', () => { modelConsumable.add( modelElement, 'type' ); @@ -153,7 +155,7 @@ describe( 'ModelConsumable', () => { } ); } ); - describe( 'revert', () => { + describe( 'revert()', () => { it( 'should re-add consumable value if it was already consumed and return true', () => { modelConsumable.add( modelElement, 'type' ); modelConsumable.consume( modelElement, 'type' ); @@ -204,7 +206,7 @@ describe( 'ModelConsumable', () => { } ); } ); - describe( 'test', () => { + describe( 'test()', () => { it( 'should return null if consumable value of given type has never been added for given element', () => { expect( modelConsumable.test( modelElement, 'typeA' ) ).to.be.null; @@ -228,4 +230,57 @@ describe( 'ModelConsumable', () => { expect( modelConsumable.test( equalProxy1To4, 'type' ) ).to.be.true; } ); } ); + + describe( 'verifyAllConsumed()', () => { + it( 'should not throw if all events were consumed', () => { + modelConsumable.add( modelElement, 'insert:paragraph' ); + modelConsumable.add( modelElement, 'attribute:foo:paragraph' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 3 ), 'insert:$text' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 3, 3 ), 'insert:$text' ); + + modelConsumable.consume( modelElement, 'insert:paragraph' ); + modelConsumable.consume( modelElement, 'attribute:foo:paragraph' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 0, 3 ), 'insert:$text' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 3, 3 ), 'insert:$text' ); + + expect( () => modelConsumable.verifyAllConsumed( 'insert' ) ).to.not.throw(); + } ); + + it( 'should not throw if all events from specified group were consumed', () => { + modelConsumable.add( modelElement, 'insert:paragraph' ); + modelConsumable.add( modelElement, 'attribute:foo:paragraph' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 3 ), 'insert:$text' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 3, 3 ), 'insert:$text' ); + + modelConsumable.consume( modelElement, 'insert:paragraph' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 0, 3 ), 'insert:$text' ); + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 3, 3 ), 'insert:$text' ); + + expect( () => modelConsumable.verifyAllConsumed( 'insert' ) ).to.not.throw(); + } ); + + it( 'should throw if some element event was not consumed', () => { + modelConsumable.add( modelElement, 'insert:paragraph' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + + modelConsumable.consume( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + + expect( () => modelConsumable.verifyAllConsumed( 'insert' ) ) + .to.throw( CKEditorError, 'conversion-model-consumable-not-consumed' ); + } ); + + it( 'should throw if some text node event was not consumed', () => { + modelConsumable.add( modelElement, 'insert:paragraph' ); + modelConsumable.add( new ModelTextProxy( modelElement.getChild( 0 ), 0, 6 ), 'insert:$text' ); + + modelConsumable.consume( modelElement, 'insert:paragraph' ); + + expect( () => modelConsumable.verifyAllConsumed( 'insert' ) ) + .to.throw( CKEditorError, 'conversion-model-consumable-not-consumed' ); + } ); + } ); } ); From 0f9e6491d8de53888bd6c54d5014f7881091cc36 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 27 Sep 2021 17:27:39 +0200 Subject: [PATCH 064/140] Added JSDocs. --- .../src/conversion/downcastdispatcher.js | 14 +++++--------- .../src/conversion/modelconsumable.js | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index 95047b7bdea..d1d4f0847db 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -70,11 +70,10 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * When providing custom listeners for a downcast dispatcher, remember to check whether a given change has not been * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} yet. * - * When providing custom listeners for downcast dispatcher, keep in mind that any callback that has - * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} a value from a consumable and - * converted the change should also stop the event (for efficiency purposes). + * When providing custom listeners for a downcast dispatcher, keep in mind that you should not stop the event. If you stop it + * then the default converter at the `lowest` priority will not trigger conversion of attributes and child nodes. * - * When providing custom listeners for downcast dispatcher, remember to use the provided + * When providing custom listeners for a downcast dispatcher, remember to use the provided * {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer} to apply changes to the view document. * * You can read more about conversion in the following guides: @@ -103,9 +102,6 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * * // Add the newly created view element to the view. * conversionApi.writer.insert( viewPosition, viewElement ); - * - * // Remember to stop the event propagation. - * evt.stop(); * } ); */ export default class DowncastDispatcher { @@ -189,7 +185,7 @@ export default class DowncastDispatcher { conversionApi.mapper.flushDeferredBindings(); // Verify if all insert consumables were consumed. - conversionApi.consumable.verifyAllConsumed( [ 'insert' ] ); + conversionApi.consumable.verifyAllConsumed( 'insert' ); } /** @@ -213,7 +209,7 @@ export default class DowncastDispatcher { } // Verify if all insert consumables were consumed. - conversionApi.consumable.verifyAllConsumed( [ 'insert' ] ); + conversionApi.consumable.verifyAllConsumed( 'insert' ); } /** diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index 149d699e087..e53efe6b3fe 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -250,16 +250,16 @@ export default class ModelConsumable { /** * Verifies if all events from specified group were consumed. * - * @param {Array.} events The events group to verify. + * @param {String} eventGroup The events group to verify. */ - verifyAllConsumed( events ) { + verifyAllConsumed( eventGroup ) { const items = []; for ( const [ item, consumables ] of this._consumable ) { for ( const [ event, canConsume ] of consumables ) { const eventPrefix = event.split( ':' )[ 0 ]; - if ( canConsume && events.includes( eventPrefix ) ) { + if ( canConsume && eventGroup == eventPrefix ) { items.push( { event, item: item.name || item.description @@ -270,7 +270,16 @@ export default class ModelConsumable { if ( items.length ) { /** - * TODO + * Some of the {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view. + * + * This might be an effect of: + * * Missing converter for some model elements. Make sure that you registered downcast converters for all model elements. + * * Custom converter that do not consume converted items. Make sure that you + * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} all model elements that you converted + * from the model to the view. + * * Custom converter that called `event.stop()`. When providing custom converter, keep in mind that you should not stop + * the event. If you stop it then the default converter at the `lowest` priority will not trigger conversion of attributes + * and child nodes. * * @error conversion-model-consumable-not-consumed */ From ab221ea9de59d0c513ab7c46656e094ada88b290 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 27 Sep 2021 18:58:26 +0200 Subject: [PATCH 065/140] Table downcast by reconversion. --- .../src/converters/downcast.js | 458 +++--------------- ...fixer.js => table-cell-refresh-handler.js} | 19 +- .../table-heading-rows-refresh-post-fixer.js | 72 --- .../table-headings-refresh-handler.js | 92 ++++ packages/ckeditor5-table/src/tableediting.js | 77 +-- .../tests/converters/downcast.js | 25 +- 6 files changed, 229 insertions(+), 514 deletions(-) rename packages/ckeditor5-table/src/converters/{table-cell-refresh-post-fixer.js => table-cell-refresh-handler.js} (68%) delete mode 100644 packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js create mode 100644 packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index c782d5cc3f7..f3c07c696a1 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -11,143 +11,64 @@ import TableWalker from './../tablewalker'; import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget'; /** - * Model table element to view table element conversion helper. - * - * This conversion helper creates the whole table element with child elements. - * - * @param {Object} options - * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. - * @returns {Function} Conversion helper. + * TODO */ -export function downcastInsertTable( options = {} ) { - return dispatcher => dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => { - const table = data.item; - - if ( !conversionApi.consumable.consume( table, 'insert' ) ) { - return; - } +export function downcastTable( tableUtils, options = {} ) { + return ( table, { writer, slotFor } ) => { + const figureElement = writer.createContainerElement( 'figure', { class: 'table' } ); + const tableElement = writer.createContainerElement( 'table' ); - // Consume attributes if present to not fire attribute change downcast - conversionApi.consumable.consume( table, 'attribute:headingRows:table' ); - conversionApi.consumable.consume( table, 'attribute:headingColumns:table' ); + writer.insert( writer.createPositionAt( figureElement, 0 ), tableElement ); - const asWidget = options && options.asWidget; + const headingRows = table.getAttribute( 'headingRows' ) || 0; - const figureElement = conversionApi.writer.createContainerElement( 'figure', { class: 'table' } ); - const tableElement = conversionApi.writer.createContainerElement( 'table' ); - conversionApi.writer.insert( conversionApi.writer.createPositionAt( figureElement, 0 ), tableElement ); + // Table head slot. + if ( headingRows > 0 ) { + const tableHead = writer.createContainerElement( 'thead' ); + const headSlot = slotFor( + element => element.is( 'element', 'tableRow' ) && element.index < headingRows + ); - let tableWidget; - - if ( asWidget ) { - tableWidget = toTableWidget( figureElement, conversionApi.writer ); + writer.insert( writer.createPositionAt( tableElement, 'end' ), tableHead ); + writer.insert( writer.createPositionAt( tableHead, 0 ), headSlot ); } - const tableWalker = new TableWalker( table ); + // Table body slot. + if ( headingRows < tableUtils.getRows( table ) ) { + const tableBody = writer.createContainerElement( 'tbody' ); + const bodySlot = slotFor( + element => element.is( 'element', 'tableRow' ) && element.index >= headingRows + ); - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; - - // Cache for created table rows. - const viewRows = new Map(); - - for ( const tableSlot of tableWalker ) { - const { row, cell } = tableSlot; - - const tableRow = table.getChild( row ); - const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); - viewRows.set( row, trElement ); - - // Consume table cell - it will be always consumed as we convert whole table at once. - conversionApi.consumable.consume( cell, 'insert' ); - - const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); - - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ); + writer.insert( writer.createPositionAt( tableElement, 'end' ), tableBody ); + writer.insert( writer.createPositionAt( tableBody, 0 ), bodySlot ); } - // Insert empty TR elements if there are any rows without anchored cells. Since the model is always normalized - // this can happen only in the document fragment that only part of the table is down-casted. - for ( const tableRow of table.getChildren() ) { - const rowIndex = tableRow.index; - - // Make sure that this is a table row and not some other element (i.e., caption). - if ( tableRow.is( 'element', 'tableRow' ) && !viewRows.has( rowIndex ) ) { - viewRows.set( rowIndex, createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) ); - } - } + // Slot for the rest (for example caption). + const restSlot = slotFor( element => !element.is( 'element', 'tableRow' ) ); - const viewPosition = conversionApi.mapper.toViewPosition( data.range.start ); + writer.insert( writer.createPositionAt( figureElement, 'end' ), restSlot ); - conversionApi.mapper.bindElements( table, asWidget ? tableWidget : figureElement ); - conversionApi.writer.insert( viewPosition, asWidget ? tableWidget : figureElement ); - } ); + return options.asWidget ? toTableWidget( figureElement, writer ) : figureElement; + }; } /** - * Model row element to view `` element conversion helper. - * - * This conversion helper creates the whole `` element with child elements. - * - * @returns {Function} Conversion helper. + * TODO */ -export function downcastInsertRow() { - return dispatcher => dispatcher.on( 'insert:tableRow', ( evt, data, conversionApi ) => { - const tableRow = data.item; - - if ( !conversionApi.consumable.consume( tableRow, 'insert' ) ) { - return; - } - - const table = tableRow.parent; - - const figureElement = conversionApi.mapper.toViewElement( table ); - const tableElement = getViewTable( figureElement ); - - const row = table.getChildIndex( tableRow ); - - const tableWalker = new TableWalker( table, { row } ); - - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; - - // Cache for created table rows. - const viewRows = new Map(); - - for ( const tableSlot of tableWalker ) { - const trElement = viewRows.get( row ) || createTr( tableElement, tableRow, row, tableAttributes, conversionApi ); - viewRows.set( row, trElement ); - - // Consume table cell - it will be always consumed as we convert whole row at once. - conversionApi.consumable.consume( tableSlot.cell, 'insert' ); - - const insertPosition = conversionApi.writer.createPositionAt( trElement, 'end' ); - - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); - } - } ); +export function downcastRow() { + return ( tableRow, { writer } ) => { + return tableRow.isEmpty ? + writer.createEmptyElement( 'tr' ) : + writer.createContainerElement( 'tr' ); + }; } /** - * Model table cell element to view ` from the view. - const removeRange = viewWriter.createRangeOn( viewItem ); - const removed = viewWriter.remove( removeRange ); - - for ( const child of viewWriter.createRangeIn( removed ).getItems() ) { - mapper.unbindViewElement( child ); - } - - // Cleanup: Ensure that thead & tbody sections are removed if left empty after removing rows. See #6437, #6391. - removeTableSectionIfEmpty( 'thead', viewTable, conversionApi ); - removeTableSectionIfEmpty( 'tbody', viewTable, conversionApi ); - }, { priority: 'higher' } ); + }; } /** @@ -245,22 +102,26 @@ export function downcastRemoveRow() { * * For a single paragraph without attributes it returns `` to simulate data table. * * For all other cases it returns `

` element. * - * @param {module:engine/model/element~Element} modelElement - * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi - * @returns {module:engine/view/containerelement~ContainerElement|undefined} + * TODO */ -export function convertParagraphInTableCell( modelElement, conversionApi ) { - const { writer } = conversionApi; +export function convertParagraphInTableCell( options = {} ) { + return ( modelElement, { writer, consumable, mapper } ) => { + if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { + return; + } - if ( !modelElement.parent.is( 'element', 'tableCell' ) ) { - return; - } + if ( !isSingleParagraphWithoutAttributes( modelElement ) ) { + return; + } - if ( isSingleParagraphWithoutAttributes( modelElement ) ) { - return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); - } else { - return writer.createContainerElement( 'p' ); - } + if ( options.asWidget ) { + return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } ); + } else { + // Additional requirement for data pipeline to have backward compatible data tables. + consumable.consume( modelElement, 'insert' ); + mapper.bindElements( modelElement, mapper.toViewElement( modelElement.parent ) ); + } + }; } /** @@ -296,205 +157,16 @@ function toTableWidget( viewElement, writer ) { return toWidget( viewElement, writer, { hasSelectionHandle: true } ); } -// Renames an existing table cell in the view to a given element name. -// -// **Note** This method will not do anything if a view table cell has not been converted yet. -// -// @param {module:engine/model/element~Element} tableCell -// @param {String} desiredCellElementName -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function renameViewTableCell( tableCell, desiredCellElementName, conversionApi ) { - const viewWriter = conversionApi.writer; - const viewCell = conversionApi.mapper.toViewElement( tableCell ); - - const editable = viewWriter.createEditableElement( desiredCellElementName, viewCell.getAttributes() ); - const renamedCell = toWidgetEditable( editable, viewWriter ); - - viewWriter.insert( viewWriter.createPositionAfter( viewCell ), renamedCell ); - viewWriter.move( viewWriter.createRangeIn( viewCell ), viewWriter.createPositionAt( renamedCell, 0 ) ); - viewWriter.remove( viewWriter.createRangeOn( viewCell ) ); - - conversionApi.mapper.unbindViewElement( viewCell ); - conversionApi.mapper.bindElements( tableCell, renamedCell ); -} - -// Renames a table cell element in the view according to its location in the table. -// -// @param {module:table/tablewalker~TableSlot} tableSlot -// @param {{headingColumns, headingRows}} tableAttributes -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ) { - const { cell } = tableSlot; - - // Check whether current columnIndex is overlapped by table cells from previous rows. - const desiredCellElementName = getCellElementName( tableSlot, tableAttributes ); - - const viewCell = conversionApi.mapper.toViewElement( cell ); - - // If in single change we're converting attribute changes and inserting cell the table cell might not be inserted into view - // because of child conversion is done after parent. - if ( viewCell && viewCell.name !== desiredCellElementName ) { - renameViewTableCell( cell, desiredCellElementName, conversionApi ); - } -} - -// Creates a table cell element in the view. -// -// @param {module:table/tablewalker~TableSlot} tableSlot -// @param {module:engine/view/position~Position} insertPosition -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, options ) { - const asWidget = options && options.asWidget; - const cellElementName = getCellElementName( tableSlot, tableAttributes ); - - const cellElement = asWidget ? - toWidgetEditable( conversionApi.writer.createEditableElement( cellElementName ), conversionApi.writer ) : - conversionApi.writer.createContainerElement( cellElementName ); - - const tableCell = tableSlot.cell; - - const firstChild = tableCell.getChild( 0 ); - const isSingleParagraph = tableCell.childCount === 1 && firstChild.name === 'paragraph'; - - conversionApi.writer.insert( insertPosition, cellElement ); - - conversionApi.mapper.bindElements( tableCell, cellElement ); - - // Additional requirement for data pipeline to have backward compatible data tables. - if ( !asWidget && isSingleParagraph && !hasAnyAttribute( firstChild ) ) { - const innerParagraph = tableCell.getChild( 0 ); - - conversionApi.consumable.consume( innerParagraph, 'insert' ); - - conversionApi.mapper.bindElements( innerParagraph, cellElement ); - } -} - -// Creates a `

` view element. -// -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/model/element~Element} tableRow -// @param {Number} rowIndex -// @param {{headingColumns, headingRows}} tableAttributes -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @returns {module:engine/view/element~Element} -function createTr( tableElement, tableRow, rowIndex, tableAttributes, conversionApi ) { - // Will always consume since we're converting element from a parent
` or `` element conversion helper. - * - * This conversion helper will create proper `` elements for table cells that are in the heading section (heading row or column) - * and `` otherwise. - * - * @returns {Function} Conversion helper. + * TODO */ -export function downcastInsertCell() { - return dispatcher => dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => { - const tableCell = data.item; - - if ( !conversionApi.consumable.consume( tableCell, 'insert' ) ) { - return; - } - +export function downcastCell( options = {} ) { + return ( tableCell, { writer } ) => { const tableRow = tableCell.parent; const table = tableRow.parent; const rowIndex = table.getChildIndex( tableRow ); @@ -159,81 +80,17 @@ export function downcastInsertCell() { headingColumns: table.getAttribute( 'headingColumns' ) || 0 }; - // We need to iterate over a table in order to get proper row & column values from a walker + // We need to iterate over a table in order to get proper row & column values from a walker. for ( const tableSlot of tableWalker ) { if ( tableSlot.cell === tableCell ) { - const trElement = conversionApi.mapper.toViewElement( tableRow ); - const insertPosition = conversionApi.writer.createPositionAt( trElement, tableRow.getChildIndex( tableCell ) ); + const cellElementName = getCellElementName( tableSlot, tableAttributes ); - createViewTableCellElement( tableSlot, tableAttributes, insertPosition, conversionApi, { asWidget: true } ); - - // No need to iterate further. - return; + return options.asWidget ? + toWidgetEditable( writer.createEditableElement( cellElementName ), writer ) : + writer.createContainerElement( cellElementName ); } } - } ); -} - -/** - * Conversion helper that acts on heading column table attribute change. - * - * Depending on changed attributes this converter will rename `` elements or vice versa depending on the cell column index. - * - * @returns {Function} Conversion helper. - */ -export function downcastTableHeadingColumnsChange() { - return dispatcher => dispatcher.on( 'attribute:headingColumns:table', ( evt, data, conversionApi ) => { - const table = data.item; - - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } - - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; - - const oldColumns = data.attributeOldValue; - const newColumns = data.attributeNewValue; - - const lastColumnToCheck = ( oldColumns > newColumns ? oldColumns : newColumns ) - 1; - - for ( const tableSlot of new TableWalker( table, { endColumn: lastColumnToCheck } ) ) { - renameViewTableCellIfRequired( tableSlot, tableAttributes, conversionApi ); - } - } ); -} - -/** - * Conversion helper that acts on a removed row. - * - * @returns {Function} Conversion helper. - */ -export function downcastRemoveRow() { - return dispatcher => dispatcher.on( 'remove:tableRow', ( evt, data, conversionApi ) => { - // Prevent default remove converter. - evt.stop(); - const viewWriter = conversionApi.writer; - const mapper = conversionApi.mapper; - - const viewStart = mapper.toViewPosition( data.position ).getLastMatchingPosition( value => !value.item.is( 'element', 'tr' ) ); - const viewItem = viewStart.nodeAfter; - const tableSection = viewItem.parent; - const viewTable = tableSection.parent; - - // Remove associated
. - conversionApi.consumable.consume( tableRow, 'insert' ); - - const trElement = tableRow.isEmpty ? - conversionApi.writer.createEmptyElement( 'tr' ) : - conversionApi.writer.createContainerElement( 'tr' ); - - conversionApi.mapper.bindElements( tableRow, trElement ); - - const headingRows = tableAttributes.headingRows; - const tableSection = getOrCreateTableSection( getSectionName( rowIndex, tableAttributes ), tableElement, conversionApi ); - - const offset = headingRows > 0 && rowIndex >= headingRows ? rowIndex - headingRows : rowIndex; - const position = conversionApi.writer.createPositionAt( tableSection, offset ); - - conversionApi.writer.insert( position, trElement ); - - return trElement; -} - // Returns `th` for heading cells and `td` for other cells for the current table walker value. // // @param {module:table/tablewalker~TableSlot} tableSlot // @param {{headingColumns, headingRows}} tableAttributes // @returns {String} -function getCellElementName( tableSlot, tableAttributes ) { - const { row, column } = tableSlot; - const { headingColumns, headingRows } = tableAttributes; - - // Column heading are all tableCells in the first `columnHeading` rows. - const isColumnHeading = headingRows && headingRows > row; - - // So a whole row gets ` or `` element with caching. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} viewTable -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @param {Object} cachedTableSection An object that stores cached elements. -// @returns {module:engine/view/containerelement~ContainerElement} -function getOrCreateTableSection( sectionName, viewTable, conversionApi ) { - const viewTableSection = getExistingTableSectionElement( sectionName, viewTable ); +function getCellElementName( { row, column }, { headingColumns, headingRows } ) { + const isColumnHeading = headingRows && row < headingRows; + const isRowHeading = headingColumns && column < headingColumns; - return viewTableSection ? viewTableSection : createTableSection( sectionName, viewTable, conversionApi ); -} - -// Finds an existing `` or `` element or returns undefined. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function getExistingTableSectionElement( sectionName, tableElement ) { - for ( const tableSection of tableElement.getChildren() ) { - if ( tableSection.name == sectionName ) { - return tableSection; - } - } -} - -// Creates a table section at the end of the table. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -// @returns {module:engine/view/containerelement~ContainerElement} -function createTableSection( sectionName, tableElement, conversionApi ) { - const tableChildElement = conversionApi.writer.createContainerElement( sectionName ); - - const insertPosition = conversionApi.writer.createPositionAt( tableElement, sectionName == 'tbody' ? 'end' : 0 ); - - conversionApi.writer.insert( insertPosition, tableChildElement ); - - return tableChildElement; -} - -// Removes an existing `` or `` element if it is empty. -// -// @param {String} sectionName -// @param {module:engine/view/element~Element} tableElement -// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi -function removeTableSectionIfEmpty( sectionName, tableElement, conversionApi ) { - const tableSection = getExistingTableSectionElement( sectionName, tableElement ); - - if ( tableSection && tableSection.childCount === 0 ) { - conversionApi.writer.remove( conversionApi.writer.createRangeOn( tableSection ) ); - } -} - -// Finds a '
element. - if ( isColumnHeading ) { - return 'th'; - } - - // Row heading are tableCells which columnIndex is lower then headingColumns. - const isRowHeading = headingColumns && headingColumns > column; - - return isRowHeading ? 'th' : 'td'; -} - -// Returns the table section name for the current table walker value. -// -// @param {Number} row -// @param {{headingColumns, headingRows}} tableAttributes -// @returns {String} -function getSectionName( row, tableAttributes ) { - return row < tableAttributes.headingRows ? 'thead' : 'tbody'; -} - -// Creates or returns an existing `
' element inside the `
` widget. -// -// @param {module:engine/view/element~Element} viewFigure -function getViewTable( viewFigure ) { - for ( const child of viewFigure.getChildren() ) { - if ( child.name === 'table' ) { - return child; - } - } + return isColumnHeading || isRowHeading ? 'th' : 'td'; } // Checks if an element has any attributes set. diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js similarity index 68% rename from packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js rename to packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js index c132e3a329a..9a82b4c1938 100644 --- a/packages/ckeditor5-table/src/converters/table-cell-refresh-post-fixer.js +++ b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js @@ -10,6 +10,8 @@ import { isSingleParagraphWithoutAttributes } from './downcast'; /** + * TODO + * * Injects a table cell post-fixer into the model which marks the table cell in the differ to have it re-rendered. * * Model `paragraph` inside a table cell can be rendered as `` or `

`. It is rendered as `` if this is the only block @@ -21,14 +23,10 @@ import { isSingleParagraphWithoutAttributes } from './downcast'; * @param {module:engine/model/model~Model} model * @param {module:engine/conversion/mapper~Mapper} mapper */ -export default function injectTableCellRefreshPostFixer( model, mapper ) { - model.document.registerPostFixer( () => tableCellRefreshPostFixer( model.document.differ, mapper ) ); -} +export default function tableCellRefreshHandler( model, mapper ) { + const differ = model.document.differ; -function tableCellRefreshPostFixer( differ, mapper ) { // Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes. - - // 1. Gather all changes inside table cell. const cellsToCheck = new Set(); for ( const change of differ.getChanges() ) { @@ -39,20 +37,11 @@ function tableCellRefreshPostFixer( differ, mapper ) { } } - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: Checking table cell to refresh (${ cellsToCheck.size }).` ); - // @if CK_DEBUG_TABLE // let paragraphsRefreshed = 0; - for ( const tableCell of cellsToCheck.values() ) { for ( const paragraph of [ ...tableCell.getChildren() ].filter( child => shouldRefresh( child, mapper ) ) ) { - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing paragraph in table cell (${++paragraphsRefreshed}).` ); differ.refreshItem( paragraph ); } } - - // Always return false to prevent the refresh post-fixer from re-running on the same set of changes and going into an infinite loop. - // This "post-fixer" does not change the model structure so there shouldn't be need to run other post-fixers again. - // See https://github.com/ckeditor/ckeditor5/issues/1936 & https://github.com/ckeditor/ckeditor5/issues/8200. - return false; } // Check if given model element needs refreshing. diff --git a/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js b/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js deleted file mode 100644 index f3a62c29f8e..00000000000 --- a/packages/ckeditor5-table/src/converters/table-heading-rows-refresh-post-fixer.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module table/converters/table-heading-rows-refresh-post-fixer - */ - -/** - * Injects a table post-fixer into the model which marks the table in the differ to have it re-rendered. - * - * Table heading rows are represented in the model by a `headingRows` attribute. However, in the view, it's represented as separate - * sections of the table (`

` or ``) and changing `headingRows` attribute requires moving table rows between two sections. - * This causes problems with structural changes in a table (like adding and removing rows) thus atomic converters cannot be used. - * - * When table `headingRows` attribute changes, the entire table is re-rendered. - * - * @param {module:engine/model/model~Model} model - */ -export default function injectTableHeadingRowsRefreshPostFixer( model ) { - model.document.registerPostFixer( () => tableHeadingRowsRefreshPostFixer( model ) ); -} - -function tableHeadingRowsRefreshPostFixer( model ) { - const differ = model.document.differ; - - // Stores tables to be refreshed so the table will be refreshed once for multiple changes. - const tablesToRefresh = new Set(); - - for ( const change of differ.getChanges() ) { - if ( change.type === 'attribute' ) { - const element = change.range.start.nodeAfter; - - if ( element && element.is( 'element', 'table' ) && change.attributeKey === 'headingRows' ) { - tablesToRefresh.add( element ); - } - } else { - /* istanbul ignore else */ - if ( change.type === 'insert' || change.type === 'remove' ) { - if ( change.name === 'tableRow' ) { - const table = change.position.findAncestor( 'table' ); - const headingRows = table.getAttribute( 'headingRows' ) || 0; - - if ( change.position.offset < headingRows ) { - tablesToRefresh.add( table ); - } - } else if ( change.name === 'tableCell' ) { - const table = change.position.findAncestor( 'table' ); - const headingColumns = table.getAttribute( 'headingColumns' ) || 0; - - if ( change.position.offset < headingColumns ) { - tablesToRefresh.add( table ); - } - } - } - } - } - - if ( tablesToRefresh.size ) { - // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing heading rows (${ tablesToRefresh.size }).` ); - - for ( const table of tablesToRefresh.values() ) { - // Should be handled by a `triggerBy` configuration. See: https://github.com/ckeditor/ckeditor5/issues/8138. - differ.refreshItem( table ); - } - - return true; - } - - return false; -} diff --git a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js new file mode 100644 index 00000000000..4106bb8b7bb --- /dev/null +++ b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js @@ -0,0 +1,92 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module table/converters/table-heading-rows-refresh-post-fixer + */ + +import TableWalker from '../tablewalker'; + +/** + * TODO + * + * Injects a table post-fixer into the model which marks the table in the differ to have it re-rendered. + * + * Table heading rows are represented in the model by a `headingRows` attribute. However, in the view, it's represented as separate + * sections of the table (`` or ``) and changing `headingRows` attribute requires moving table rows between two sections. + * This causes problems with structural changes in a table (like adding and removing rows) thus atomic converters cannot be used. + * + * When table `headingRows` attribute changes, the entire table is re-rendered. + * + * @param {module:engine/model/model~Model} model + */ +export default function tableHeadingsRefreshHandler( model, mapper ) { + const differ = model.document.differ; + + for ( const change of differ.getChanges() ) { + if ( change.type == 'attribute' ) { + const element = change.range.start.nodeAfter; + + if ( !element || !element.is( 'element', 'table' ) ) { + continue; + } + + if ( change.attributeKey == 'headingRows' ) { + reconvertOnAttributeChange( differ, change, element, 'startRow', 'endRow' ); + } else if ( change.attributeKey == 'headingColumns' ) { + reconvertOnAttributeChange( differ, change, element, 'startColumn', 'endColumn' ); + } + } else if ( change.type == 'insert' ) { + // TODO this does not work + if ( change.name == 'tableRow' ) { + const table = change.position.findAncestor( 'table' ); + const headingRows = table.getAttribute( 'headingRows' ) || 0; + const rowIndex = change.position.offset; + const isHeading = rowIndex < headingRows; + + const tableRowElement = change.position.nodeAfter; + const viewElement = mapper.toViewElement( tableRowElement.getChild( tableRowElement.childCount - 1 ) ); + + if ( viewElement && viewElement.is( 'element', isHeading ? 'td' : 'th' ) ) { + reconvertCells( differ, table, { + startRow: rowIndex, + endRow: rowIndex + 1 + } ); + } + } else if ( change.name == 'tableCell' ) { + const table = change.position.findAncestor( 'table' ); + const headingColumns = table.getAttribute( 'headingColumns' ) || 0; + const columnIndex = change.position.offset; + + if ( columnIndex < headingColumns ) { + reconvertCells( differ, table, { + startColumn: columnIndex, + endColumn: columnIndex + 1 + } ); + } + } + } + } +} + +// TODO +function reconvertOnAttributeChange( differ, change, table, startOption, endOption ) { + const oldHeadings = change.attributeOldValue || 0; + const newHeadings = change.attributeNewValue || 0; + + reconvertCells( differ, table, { + [ startOption ]: Math.min( oldHeadings, newHeadings ), + [ endOption ]: Math.max( oldHeadings, newHeadings ) - 1 + } ); +} + +// TODO +function reconvertCells( differ, table, options ) { + const tableWalker = new TableWalker( table, options ); + + for ( const tableSlot of tableWalker ) { + differ.refreshItem( tableSlot.cell ); + } +} diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index 838d1643f16..bc2153e09bc 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -10,14 +10,7 @@ import { Plugin } from 'ckeditor5/src/core'; import upcastTable, { ensureParagraphInTableCell, skipEmptyTableRow, upcastTableFigure } from './converters/upcasttable'; -import { - convertParagraphInTableCell, - downcastInsertCell, - downcastInsertRow, - downcastInsertTable, - downcastRemoveRow, - downcastTableHeadingColumnsChange -} from './converters/downcast'; +import { convertParagraphInTableCell, downcastCell, downcastRow, downcastTable } from './converters/downcast'; import InsertTableCommand from './commands/inserttablecommand'; import InsertRowCommand from './commands/insertrowcommand'; @@ -35,8 +28,9 @@ import TableUtils from '../src/tableutils'; import injectTableLayoutPostFixer from './converters/table-layout-post-fixer'; import injectTableCellParagraphPostFixer from './converters/table-cell-paragraph-post-fixer'; -import injectTableCellRefreshPostFixer from './converters/table-cell-refresh-post-fixer'; -import injectTableHeadingRowsRefreshPostFixer from './converters/table-heading-rows-refresh-post-fixer'; + +import tableHeadingsRefreshHandler from './converters/table-headings-refresh-handler'; +import tableCellRefreshHandler from './converters/table-cell-refresh-handler'; import '../theme/tableediting.css'; @@ -53,6 +47,13 @@ export default class TableEditing extends Plugin { return 'TableEditing'; } + /** + * @inheritDoc + */ + static get requires() { + return [ TableUtils ]; + } + /** * @inheritDoc */ @@ -61,6 +62,7 @@ export default class TableEditing extends Plugin { const model = editor.model; const schema = model.schema; const conversion = editor.conversion; + const tableUtils = editor.plugins.get( TableUtils ); schema.register( 'table', { allowWhere: '$block', @@ -88,15 +90,31 @@ export default class TableEditing extends Plugin { // Table conversion. conversion.for( 'upcast' ).add( upcastTable() ); - conversion.for( 'editingDowncast' ).add( downcastInsertTable( { asWidget: true } ) ); - conversion.for( 'dataDowncast' ).add( downcastInsertTable() ); + conversion.for( 'editingDowncast' ).elementToStructure( { + model: { + name: 'table', + attributes: [ 'headingRows' ], + children: true + }, + view: downcastTable( tableUtils, { asWidget: true } ) + } ); + conversion.for( 'dataDowncast' ).elementToStructure( { + model: { + name: 'table', + attributes: [ 'headingRows' ], + children: true + }, + view: downcastTable( tableUtils ) + } ); // Table row conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableRow', view: 'tr' } ); conversion.for( 'upcast' ).add( skipEmptyTableRow() ); - conversion.for( 'editingDowncast' ).add( downcastInsertRow() ); - conversion.for( 'editingDowncast' ).add( downcastRemoveRow() ); + conversion.for( 'downcast' ).elementToElement( { + model: 'tableRow', + view: downcastRow() + } ); // Table cell conversion. conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'td' } ); @@ -104,12 +122,24 @@ export default class TableEditing extends Plugin { conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'td' ) ); conversion.for( 'upcast' ).add( ensureParagraphInTableCell( 'th' ) ); - conversion.for( 'editingDowncast' ).add( downcastInsertCell() ); + conversion.for( 'editingDowncast' ).elementToElement( { + model: 'tableCell', + view: downcastCell( { asWidget: true } ) + } ); + conversion.for( 'dataDowncast' ).elementToElement( { + model: 'tableCell', + view: downcastCell() + } ); // Duplicates code - needed to properly refresh paragraph inside a table cell. conversion.for( 'editingDowncast' ).elementToElement( { model: 'paragraph', - view: convertParagraphInTableCell, + view: convertParagraphInTableCell( { asWidget: true } ), + converterPriority: 'high' + } ); + conversion.for( 'dataDowncast' ).elementToElement( { + model: 'paragraph', + view: convertParagraphInTableCell(), converterPriority: 'high' } ); @@ -126,9 +156,6 @@ export default class TableEditing extends Plugin { view: 'rowspan' } ); - // Table heading columns conversion (a change of heading rows requires a reconversion of the whole table). - conversion.for( 'editingDowncast' ).add( downcastTableHeadingColumnsChange() ); - // Manually adjust model position mappings in a special case, when a table cell contains a paragraph, which is bound // to its parent (to the table cell). This custom model-to-view position mapping is necessary in data pipeline only, // because only during this conversion a paragraph can be bound to its parent. @@ -164,17 +191,13 @@ export default class TableEditing extends Plugin { editor.commands.add( 'selectTableRow', new SelectRowCommand( editor ) ); editor.commands.add( 'selectTableColumn', new SelectColumnCommand( editor ) ); - injectTableHeadingRowsRefreshPostFixer( model ); injectTableLayoutPostFixer( model ); - injectTableCellRefreshPostFixer( model, editor.editing.mapper ); injectTableCellParagraphPostFixer( model ); - } - /** - * @inheritDoc - */ - static get requires() { - return [ TableUtils ]; + this.listenTo( model.document, 'change:data', () => { + tableHeadingsRefreshHandler( model, editor.editing.mapper ); + tableCellRefreshHandler( model, editor.editing.mapper ); + } ); } } diff --git a/packages/ckeditor5-table/tests/converters/downcast.js b/packages/ckeditor5-table/tests/converters/downcast.js index 4741897502b..7e8b2d951ee 100644 --- a/packages/ckeditor5-table/tests/converters/downcast.js +++ b/packages/ckeditor5-table/tests/converters/downcast.js @@ -8,7 +8,7 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; -import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; import { modelTable, viewTable } from '../_utils/utils'; @@ -479,8 +479,6 @@ describe( 'downcast converters', () => { writer.insertElement( 'tableCell', row, 'end' ); writer.insertElement( 'tableCell', row, 'end' ); - - writer.setAttribute( 'headingRows', 3, table ); } ); assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ @@ -812,10 +810,17 @@ describe( 'downcast converters', () => { } ); it( 'should work with adding table cells', () => { + // +----+----+----+----+ + // | 00 | 01 | 02 | 03 | + // + +----+----+----+ + // | | 11 | 12 | 13 | + // +----+----+----+----+ + // | 20 | 22 | 23 | + // +----+----+----+----+ setModelData( model, modelTable( [ - [ { rowspan: 2, contents: '00' }, '01', '13', '14' ], + [ { contents: '00', rowspan: 2 }, '01', '02', '03' ], [ '11', '12', '13' ], - [ { colspan: 2, contents: '20' }, '22', '23' ] + [ { contents: '20', colspan: 2 }, '22', '23' ] ], { headingColumns: 2 } ) ); const table = root.getChild( 0 ); @@ -829,13 +834,19 @@ describe( 'downcast converters', () => { writer.insertElement( 'tableCell', table.getChild( 2 ), 1 ); } ); + assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [ + [ { contents: '00', rowspan: 2 }, '01', '', '02', '03' ], + [ '11', '', '12', '13' ], + [ { contents: '20', colspan: 2 }, '', '22', '23' ] + ], { headingColumns: 3 } ) ); + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ [ { isHeading: true, rowspan: 2, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '' }, - '13', - '14' + '02', + '03' ], [ { isHeading: true, contents: '11' }, From 78a9f6166f06e79c2bb19caa5197e43ae5f805d4 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 29 Sep 2021 16:32:30 +0200 Subject: [PATCH 066/140] Changed refreshing of table cells. --- .../ckeditor5-engine/src/conversion/mapper.js | 23 ++++++++++ packages/ckeditor5-engine/src/model/differ.js | 10 ++++ .../table-headings-refresh-handler.js | 46 ++++++++----------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 1959fbb0459..54825d52323 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -288,6 +288,29 @@ export default class Mapper { this._deferredBindingRemovals = new Map(); } + /** + * TODO + */ + flushDeferredBindingsForElements( modelElements ) { + for ( const modelElement of modelElements ) { + const viewElement = this._modelToViewMapping.get( modelElement ); + + if ( !viewElement ) { + continue; + } + + const root = this._deferredBindingRemovals.get( viewElement ); + + // Unbind it only if it wasn't re-attached to some root or document fragment. + if ( !root || viewElement.root != root ) { + continue; + } + + this.unbindViewElement( viewElement ); + this._deferredBindingRemovals.delete( viewElement ); + } + } + /** * Removes all model to view and view to model bindings. */ diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 32a4ad1c18a..7227b2100be 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -98,6 +98,13 @@ export default class Differ { * @type {Array.|null} */ this._cachedChangesWithGraveyard = null; + + /** + * TODO + * + * @private + */ + this._invalidatedMappings = new Set(); } /** @@ -124,6 +131,8 @@ export default class Differ { this._markRemove( item.parent, item.startOffset, item.offsetSize ); this._markInsert( item.parent, item.startOffset, item.offsetSize ); + this._invalidatedMappings.add( item ); + const range = Range._createOn( item ); for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) { @@ -557,6 +566,7 @@ export default class Differ { this._changesInElement.clear(); this._elementSnapshots.clear(); this._changedMarkers.clear(); + this._invalidatedMappings = new Set(); this._cachedChanges = null; } diff --git a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js index 4106bb8b7bb..8edfb475951 100644 --- a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js @@ -38,33 +38,28 @@ export default function tableHeadingsRefreshHandler( model, mapper ) { } else if ( change.attributeKey == 'headingColumns' ) { reconvertOnAttributeChange( differ, change, element, 'startColumn', 'endColumn' ); } - } else if ( change.type == 'insert' ) { - // TODO this does not work - if ( change.name == 'tableRow' ) { + } else { + /* istanbul ignore else */ + if ( change.type == 'insert' || change.type == 'remove' ) { + if ( change.name != 'tableRow' && change.name != 'tableCell' ) { + continue; + } + const table = change.position.findAncestor( 'table' ); const headingRows = table.getAttribute( 'headingRows' ) || 0; - const rowIndex = change.position.offset; - const isHeading = rowIndex < headingRows; + const headingColumns = table.getAttribute( 'headingColumns' ) || 0; - const tableRowElement = change.position.nodeAfter; - const viewElement = mapper.toViewElement( tableRowElement.getChild( tableRowElement.childCount - 1 ) ); + const tableWalker = new TableWalker( table ); - if ( viewElement && viewElement.is( 'element', isHeading ? 'td' : 'th' ) ) { - reconvertCells( differ, table, { - startRow: rowIndex, - endRow: rowIndex + 1 - } ); - } - } else if ( change.name == 'tableCell' ) { - const table = change.position.findAncestor( 'table' ); - const headingColumns = table.getAttribute( 'headingColumns' ) || 0; - const columnIndex = change.position.offset; + for ( const tableSlot of tableWalker ) { + const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns; + const expectedElementName = isHeading ? 'th' : 'td'; + + const viewElement = mapper.toViewElement( tableSlot.cell ); - if ( columnIndex < headingColumns ) { - reconvertCells( differ, table, { - startColumn: columnIndex, - endColumn: columnIndex + 1 - } ); + if ( viewElement && viewElement.is( 'element' ) && viewElement.name != expectedElementName ) { + differ.refreshItem( change.name == 'tableRow' ? tableSlot.cell.parent : tableSlot.cell ); + } } } } @@ -76,15 +71,10 @@ function reconvertOnAttributeChange( differ, change, table, startOption, endOpti const oldHeadings = change.attributeOldValue || 0; const newHeadings = change.attributeNewValue || 0; - reconvertCells( differ, table, { + const tableWalker = new TableWalker( table, { [ startOption ]: Math.min( oldHeadings, newHeadings ), [ endOption ]: Math.max( oldHeadings, newHeadings ) - 1 } ); -} - -// TODO -function reconvertCells( differ, table, options ) { - const tableWalker = new TableWalker( table, options ); for ( const tableSlot of tableWalker ) { differ.refreshItem( tableSlot.cell ); From 2059903c1ecf81b864bd834a2eeed4914a4b5267 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 29 Sep 2021 16:33:18 +0200 Subject: [PATCH 067/140] Changed refreshing of table cells. --- packages/ckeditor5-engine/src/conversion/downcastdispatcher.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index ad5e5d66834..fb92b30eaaf 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -171,6 +171,9 @@ export default class DowncastDispatcher { // Defaults to 'attribute' change. this._convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, conversionApi ); } + + // TODO access _invalidatedMappings with some public method + conversionApi.mapper.flushDeferredBindingsForElements( differ._invalidatedMappings ); } for ( const markerName of conversionApi.mapper.flushUnboundMarkerNames() ) { From 99872b9bdfd297ad46bc85298145d42b0a470e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Mon, 4 Oct 2021 12:22:57 +0200 Subject: [PATCH 068/140] Wording. --- .../src/conversion/downcastdispatcher.js | 4 ++-- .../src/conversion/modelconsumable.js | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index d1d4f0847db..5bc9e6806e9 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -70,8 +70,8 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * When providing custom listeners for a downcast dispatcher, remember to check whether a given change has not been * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} yet. * - * When providing custom listeners for a downcast dispatcher, keep in mind that you should not stop the event. If you stop it - * then the default converter at the `lowest` priority will not trigger conversion of attributes and child nodes. + * When providing custom listeners for a downcast dispatcher, keep in mind that you **should not** stop the event. If you stop it, + * then the default converter at the `lowest` priority will not trigger the conversion of this node's attributes and child nodes. * * When providing custom listeners for a downcast dispatcher, remember to use the provided * {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer} to apply changes to the view document. diff --git a/packages/ckeditor5-engine/src/conversion/modelconsumable.js b/packages/ckeditor5-engine/src/conversion/modelconsumable.js index e53efe6b3fe..405adcd44a5 100644 --- a/packages/ckeditor5-engine/src/conversion/modelconsumable.js +++ b/packages/ckeditor5-engine/src/conversion/modelconsumable.js @@ -270,18 +270,20 @@ export default class ModelConsumable { if ( items.length ) { /** - * Some of the {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view. + * Some of {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view. * * This might be an effect of: + * * * Missing converter for some model elements. Make sure that you registered downcast converters for all model elements. - * * Custom converter that do not consume converted items. Make sure that you + * * Custom converter that does not consume converted items. Make sure that you * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} all model elements that you converted * from the model to the view. - * * Custom converter that called `event.stop()`. When providing custom converter, keep in mind that you should not stop - * the event. If you stop it then the default converter at the `lowest` priority will not trigger conversion of attributes - * and child nodes. + * * Custom converter that called `event.stop()`. When providing a custom converter, keep in mind that you should not stop + * the event. If you stop it then the default converter at the `lowest` priority will not trigger the conversion of this node's + * attributes and child nodes. * * @error conversion-model-consumable-not-consumed + * @param {Array.} items Items that were not consumed. */ throw new CKEditorError( 'conversion-model-consumable-not-consumed', null, { items } ); } From bea119ef46d994c7f66fa9cc5c0e918835e69708 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 4 Oct 2021 16:39:50 +0200 Subject: [PATCH 069/140] Downcast should not reuse view nodes that were marked by Differ#refreshItem(). --- .../src/conversion/downcastdispatcher.js | 14 ++--- .../src/conversion/downcasthelpers.js | 56 ++++++++++++++----- .../ckeditor5-engine/src/conversion/mapper.js | 23 -------- packages/ckeditor5-engine/src/model/differ.js | 18 ++++-- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index fb92b30eaaf..38f29db9421 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -149,7 +149,7 @@ export default class DowncastDispatcher { * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document. */ convertChanges( differ, markers, writer ) { - const conversionApi = this._createConversionApi( writer ); + const conversionApi = this._createConversionApi( writer, differ.getRefreshedItems() ); // Before the view is updated, remove markers which have changed. for ( const change of differ.getMarkersToRemove() ) { @@ -171,9 +171,6 @@ export default class DowncastDispatcher { // Defaults to 'attribute' change. this._convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, conversionApi ); } - - // TODO access _invalidatedMappings with some public method - conversionApi.mapper.flushDeferredBindingsForElements( differ._invalidatedMappings ); } for ( const markerName of conversionApi.mapper.flushUnboundMarkerNames() ) { @@ -204,7 +201,7 @@ export default class DowncastDispatcher { * @param {Object} [options] Optional options object passed to `convertionApi.options`. */ convert( range, markers, writer, options = {} ) { - const conversionApi = this._createConversionApi( writer, options ); + const conversionApi = this._createConversionApi( writer, undefined, options ); this._convertInsert( range, conversionApi ); @@ -580,10 +577,12 @@ export default class DowncastDispatcher { * * @private * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. + * @param {Set.} [invalidatedMappings] A set of model elements that should not reuse their + * previous view representations. * @param {Object} [options] Optional options passed to `convertionApi.options`. * @return {module:engine/conversion/downcastdispatcher~DowncastConversionApi} The conversion API object. */ - _createConversionApi( writer, options = {} ) { + _createConversionApi( writer, invalidatedMappings = new Set(), options = {} ) { const conversionApi = { ...this._conversionApi, consumable: new Consumable(), @@ -591,7 +590,8 @@ export default class DowncastDispatcher { options, convertItem: item => this._convertInsert( Range._createOn( item ), conversionApi ), convertChildren: element => this._convertInsert( Range._createIn( element ), conversionApi, { doNotAddConsumables: true } ), - convertAttributes: item => this._testAndFireAddAttributes( item, conversionApi ) + convertAttributes: item => this._testAndFireAddAttributes( item, conversionApi ), + canReuseView: viewElement => !invalidatedMappings.has( conversionApi.mapper.toModelElement( viewElement ) ) }; this._firedEventsMap.set( conversionApi, new Map() ); diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 3db1f388a77..5a45865fd7b 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1052,7 +1052,7 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); - reinsertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); + reinsertOrConvertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } @@ -2184,7 +2184,7 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { // Fill slots with nested view nodes. for ( [ currentSlot, currentSlotNodes ] of slotsMap ) { - reinsertNodes( viewElement, currentSlotNodes, conversionApi, options ); + reinsertOrConvertNodes( viewElement, currentSlotNodes, conversionApi, options ); conversionApi.writer.move( conversionApi.writer.createRangeIn( currentSlot ), @@ -2217,24 +2217,54 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) { // @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi // @param {Object} options // @param {Boolean} [options.reconversion] -function reinsertNodes( viewElement, modelNodes, conversionApi, options ) { - const { writer, mapper } = conversionApi; - +function reinsertOrConvertNodes( viewElement, modelNodes, conversionApi, options ) { // Fill with nested view nodes. for ( const modelChildNode of modelNodes ) { - const viewChildNode = mapper.toViewElement( modelChildNode ); - - if ( options.reconversion && viewChildNode && viewChildNode.root != viewElement.root ) { - writer.move( - writer.createRangeOn( viewChildNode ), - mapper.toViewPosition( ModelPosition._createBefore( modelChildNode ) ) - ); - } else { + // Try reinserting the view node for the specified model node... + if ( !reinsertNode( viewElement.root, modelChildNode, conversionApi, options ) ) { + // ...or else convert the model element to the view. conversionApi.convertItem( modelChildNode ); } } } +// Checks if the view for the given model element could be reused and reinserts it to the view. +// +// @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} viewRoot +// @param {module:engine/model/element~Element} modelElement +// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi +// @param {Object} options +// @param {Boolean} [options.reconversion] +// @returns {Boolean} `false` if view element can't be reused. +function reinsertNode( viewRoot, modelElement, conversionApi, options ) { + const { writer, mapper } = conversionApi; + + // Don't reinsert if this is not a reconversion... + if ( !options.reconversion ) { + return false; + } + + const viewChildNode = mapper.toViewElement( modelElement ); + + // ...or there is no view to reinsert or it was already inserted to the view structure... + if ( !viewChildNode || viewChildNode.root == viewRoot ) { + return false; + } + + // ...or it was strictly marked as not to be reused. + if ( !conversionApi.canReuseView( viewChildNode ) ) { + return false; + } + + // Otherwise reinsert the view node. + writer.move( + writer.createRangeOn( viewChildNode ), + mapper.toViewPosition( ModelPosition._createBefore( modelElement ) ) + ); + + return true; +} + // The default consumer for insert events. // @param {module:engine/model/item~Item} item Model item. // @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The model consumable. diff --git a/packages/ckeditor5-engine/src/conversion/mapper.js b/packages/ckeditor5-engine/src/conversion/mapper.js index 54825d52323..1959fbb0459 100644 --- a/packages/ckeditor5-engine/src/conversion/mapper.js +++ b/packages/ckeditor5-engine/src/conversion/mapper.js @@ -288,29 +288,6 @@ export default class Mapper { this._deferredBindingRemovals = new Map(); } - /** - * TODO - */ - flushDeferredBindingsForElements( modelElements ) { - for ( const modelElement of modelElements ) { - const viewElement = this._modelToViewMapping.get( modelElement ); - - if ( !viewElement ) { - continue; - } - - const root = this._deferredBindingRemovals.get( viewElement ); - - // Unbind it only if it wasn't re-attached to some root or document fragment. - if ( !root || viewElement.root != root ) { - continue; - } - - this.unbindViewElement( viewElement ); - this._deferredBindingRemovals.delete( viewElement ); - } - } - /** * Removes all model to view and view to model bindings. */ diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 7227b2100be..befd1db4e16 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -100,11 +100,12 @@ export default class Differ { this._cachedChangesWithGraveyard = null; /** - * TODO + * Set of model items that were marked to get refreshed. * * @private + * @type {Set.} */ - this._invalidatedMappings = new Set(); + this._refreshedItems = new Set(); } /** @@ -131,7 +132,7 @@ export default class Differ { this._markRemove( item.parent, item.startOffset, item.offsetSize ); this._markInsert( item.parent, item.startOffset, item.offsetSize ); - this._invalidatedMappings.add( item ); + this._refreshedItems.add( item ); const range = Range._createOn( item ); @@ -559,6 +560,15 @@ export default class Differ { } } + /** + * Returns a set of model items that were marked to get refreshed. + * + * @return {Set.} + */ + getRefreshedItems() { + return new Set( this._refreshedItems ); + } + /** * Resets `Differ`. Removes all buffered changes. */ @@ -566,7 +576,7 @@ export default class Differ { this._changesInElement.clear(); this._elementSnapshots.clear(); this._changedMarkers.clear(); - this._invalidatedMappings = new Set(); + this._refreshedItems = new Set(); this._cachedChanges = null; } From f8a5625551592666c9279b57440c939c2d53f18e Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Tue, 5 Oct 2021 19:09:30 +0200 Subject: [PATCH 070/140] Updated tests. --- .../tests/conversion/downcastdispatcher.js | 3 +- .../tests/conversion/downcasthelpers.js | 114 +++++++++++++++++- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js index 163dc61adc2..f48d08fac23 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js @@ -36,7 +36,8 @@ describe( 'DowncastDispatcher', () => { differStub = { getMarkersToRemove: () => [], getChanges: () => [], - getMarkersToAdd: () => [] + getMarkersToAdd: () => [], + getRefreshedItems: () => [] }; } ); diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 3923d0192ca..475aed7f21b 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -293,6 +293,53 @@ describe( 'DowncastHelpers', () => { expectResult( '
' ); } ); + it( 'should reuse child view element', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block', allowIn: 'simpleBlock' } ); + downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); + + setModelData( model, 'foo' ); + + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; + } ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + expect( paraAfter ).to.equal( paraBefore ); + expect( textAfter ).to.equal( textBefore ); + } ); + + it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block', allowIn: 'simpleBlock' } ); + downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); + + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + expect( paraAfter ).to.not.equal( paraBefore ); + expect( textAfter ).to.not.equal( textBefore ); + } ); + it( 'should properly re-bind mapper mappings and retain markers', () => { downcastHelpers.elementToElement( { model: 'simpleBlock', @@ -394,7 +441,8 @@ describe( 'DowncastHelpers', () => { describe( 'with simple block view structure (with reconvertion on child add)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { - allowIn: '$root' + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] } ); downcastHelpers.elementToElement( { @@ -403,7 +451,7 @@ describe( 'DowncastHelpers', () => { children: true }, view: ( modelElement, { writer } ) => { - return writer.createContainerElement( 'div' ); + return writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); } } ); @@ -642,6 +690,25 @@ describe( 'DowncastHelpers', () => { expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); } ); + + it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + expect( paraAfter ).to.not.equal( paraBefore ); + expect( textAfter ).to.not.equal( textBefore ); + } ); } ); } ); @@ -1138,12 +1205,32 @@ describe( 'DowncastHelpers', () => { expect( viewAfter ).to.equal( viewBefore ); } ); + + it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + expect( paraAfter ).to.not.equal( paraBefore ); + expect( textAfter ).to.not.equal( textBefore ); + } ); } ); describe( 'with simple block view structure (reconvert on child add)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { - allowIn: '$root' + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] } ); downcastHelpers.elementToStructure( { @@ -1152,7 +1239,7 @@ describe( 'DowncastHelpers', () => { children: true }, view: ( modelElement, { writer, slotFor } ) => { - const viewElement = writer.createContainerElement( 'div' ); + const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); writer.insert( writer.createPositionAt( viewElement, 0 ), slotFor( 'children' ) ); @@ -1399,6 +1486,25 @@ describe( 'DowncastHelpers', () => { expect( paraAfterAfter1, 'para' ).to.equal( paraAfter1 ); expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); } ); + + it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + setModelData( model, 'foo' ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); + model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + } ); + + const [ viewAfter, paraAfter, textAfter ] = getNodes(); + + expectResult( '

foo

' ); + + expect( viewAfter ).to.not.equal( viewBefore ); + expect( paraAfter ).to.not.equal( paraBefore ); + expect( textAfter ).to.not.equal( textBefore ); + } ); } ); describe( 'with complex view structure - single slot for all children', () => { From 70311c038ea27bbc25c69d391201317cdb098267 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 6 Oct 2021 17:28:15 +0200 Subject: [PATCH 071/140] Adjusted to upstream changes. --- .../src/controller/datacontroller.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js index 48168230f2e..9e59642d0e4 100644 --- a/packages/ckeditor5-engine/src/controller/datacontroller.js +++ b/packages/ckeditor5-engine/src/controller/datacontroller.js @@ -523,11 +523,11 @@ mix( DataController, ObservableMixin ); // at element boundary, it is considered as contained inside the element and marker range is returned. Otherwise, if the marker is // intersecting with the element, the intersection is returned. function _getMarkersRelativeToElement( element ) { - const result = new Map(); + const result = []; const doc = element.root.document; if ( !doc ) { - return result; + return new Map(); } const elementRange = ModelRange._createIn( element ); @@ -539,12 +539,12 @@ function _getMarkersRelativeToElement( element ) { const isMarkerAtElementBoundary = markerRange.start.isEqual( elementRange.start ) || markerRange.end.isEqual( elementRange.end ); if ( isMarkerCollapsed && isMarkerAtElementBoundary ) { - result.set( marker.name, markerRange ); + result.push( [ marker.name, markerRange ] ); } else { const updatedMarkerRange = elementRange.getIntersection( markerRange ); if ( updatedMarkerRange ) { - result.set( marker.name, updatedMarkerRange ); + result.push( [ marker.name, updatedMarkerRange ] ); } } } @@ -553,15 +553,15 @@ function _getMarkersRelativeToElement( element ) { // added to the model's marker collection does not affect how they are // downcast. One particular use case that we're targeting here is one where // two markers are adjacent but not overlapping, such as an insertion/deletion - // suggestion pair represting the replacement of a range of text. In this + // suggestion pair representing the replacement of a range of text. In this // case, putting the markers in DOM order causes the first marker's end to be // serialized right after the second marker's start, while putting the markers // in reverse DOM order causes it to be right before the second marker's // start. So, we sort in a way that ensures non-intersecting ranges are in // reverse DOM order, and intersecting ranges are in something approximating // reverse DOM order (since reverse DOM order doesn't have a precise meaning - // when working with intersectng ranges). - return result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => { + // when working with intersecting ranges). + result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => { if ( r1.end.compareWith( r2.start ) !== 'after' ) { // m1.end <= m2.start -- m1 is entirely <= m2 return 1; @@ -588,4 +588,6 @@ function _getMarkersRelativeToElement( element ) { } } } ); + + return new Map( result ); } From be305bb4437c44557515063e646bf2895b16bcfb Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 6 Oct 2021 17:58:36 +0200 Subject: [PATCH 072/140] The reconversion "children" flag is optional and implicit in some cases. --- .../src/conversion/downcasthelpers.js | 21 +- .../tests/conversion/downcasthelpers.js | 196 ++++++++++++------ 2 files changed, 152 insertions(+), 65 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 5a45865fd7b..21af8442f58 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1052,6 +1052,9 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); + // Convert attributes before converting children. + conversionApi.convertAttributes( data.item ); + reinsertOrConvertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } @@ -1100,6 +1103,9 @@ export function insertStructure( elementCreator, consumer ) { conversionApi.mapper.bindElements( data.item, viewElement ); conversionApi.writer.insert( viewPosition, viewElement ); + // Convert attributes before converting children. + conversionApi.convertAttributes( data.item ); + // Fill view slots with previous view elements or create new ones. fillSlots( viewElement, slotsMap, conversionApi, { reconversion: data.reconversion } ); }; @@ -1654,6 +1660,12 @@ function downcastElementToElement( config ) { config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); + // Trigger reconversion on children list change if element is a subject to any reconversion. + // This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element. + if ( config.model.attributes.length ) { + config.model.children = true; + } + return dispatcher => { dispatcher.on( 'insert:' + config.model.name, @@ -1684,6 +1696,10 @@ function downcastElementToStructure( config ) { config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); + // Trigger reconversion on children list change because it always needs to use slots to put children in proper places. + // This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element. + config.model.children = true; + return dispatcher => { dispatcher.on( 'insert:' + config.model.name, @@ -1691,9 +1707,7 @@ function downcastElementToStructure( config ) { { priority: config.converterPriority || 'normal' } ); - if ( config.model.children || config.model.attributes.length ) { - dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); - } + dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); }; } @@ -2013,6 +2027,7 @@ function createChangeReducerCallback( model ) { return true; } } else { + /* istanbul ignore else: This is always true because otherwise it would not register a reducer callback. */ if ( model.children ) { return true; } diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index a33503a0417..11e1ee3bab2 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -137,6 +137,67 @@ describe( 'DowncastHelpers', () => { expectResult( '' ); } ); + describe( 'converting element itself', () => { + beforeEach( () => { + model.schema.register( 'simpleBlock', { + allowIn: '$root', + allowAttributes: [ 'toStyle', 'toClass' ] + } ); + + downcastHelpers.elementToElement( { + model: 'simpleBlock', + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', { class: 'simple' } ); + } + } ); + + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'simpleBlock' + } ); + + downcastHelpers.elementToElement( { + model: 'paragraph', + view: 'p' + } ); + } ); + + it( 'should convert element insert', () => { + setModelData( model, '' ); + + expectResult( '
' ); + } ); + + it( 'should not reconvert on adding a child', () => { + setModelData( model, 'foo' ); + + const spy = sinon.spy(); + + controller.downcastDispatcher.on( 'insert:simpleBlock', () => { + spy(); + } ); + + const [ viewBefore, paraBefore, textBefore ] = getNodes(); + + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( 'bar' ); + + writer.insert( paragraph, modelRoot.getChild( 0 ), 0 ); + writer.insert( text, paragraph, 0 ); + } ); + + const [ viewAfter, /* insertedPara */, /* insertedText */, paraAfter, textAfter ] = getNodes(); + + expectResult( '

bar

foo

' ); + + expect( viewAfter, 'simpleBlock' ).to.equal( viewBefore ); + expect( paraAfter, 'para' ).to.equal( paraBefore ); + expect( textAfter, 'text' ).to.equal( textBefore ); + expect( spy.notCalled ).to.be.true; + } ); + } ); + describe( 'converting element together with selected attributes', () => { it( 'should allow passing a list of attributes to convert and consume', () => { model.schema.register( 'simpleBlock', { @@ -199,7 +260,7 @@ describe( 'DowncastHelpers', () => { } ); } ); - describe( 'with simple block view structure (without reconvertion on children list change)', () => { + describe( 'with simple block view structure (without reconversion on children list change)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -411,7 +472,7 @@ describe( 'DowncastHelpers', () => { expect( viewAfter ).to.equal( viewBefore ); } ); - it( 'should not reconvert on child element added', () => { + it( 'should reconvert on child element added (implicit reconversion because of attributes watch)', () => { model.schema.register( 'paragraph', { inheritAllFrom: '$block', allowIn: 'simpleBlock' @@ -424,8 +485,8 @@ describe( 'DowncastHelpers', () => { setModelData( model, '' ); - controller.downcastDispatcher.on( 'insert', ( evt, data ) => { - expect( data ).to.not.have.property( 'reconversion' ); + controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => { + expect( data ).to.have.property( 'reconversion' ).to.be.true; } ); const [ viewBefore ] = getNodes(); @@ -438,11 +499,11 @@ describe( 'DowncastHelpers', () => { expectResult( '

' ); - expect( viewAfter ).to.equal( viewBefore ); + expect( viewAfter ).to.not.equal( viewBefore ); } ); } ); - describe( 'with simple block view structure (with reconvertion on child add)', () => { + describe( 'with simple block view structure (with reconversion on child add)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -713,6 +774,37 @@ describe( 'DowncastHelpers', () => { expect( paraAfter ).to.not.equal( paraBefore ); expect( textAfter ).to.not.equal( textBefore ); } ); + + it( 'should fire conversion events in proper order', () => { + const loggedEvents = []; + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + }, { priority: 'highest' } ); + + controller.downcastDispatcher.on( 'attribute', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + }, { priority: 'highest' } ); + + setModelData( model, 'foo' ); + + expectResult( '

foo

' ); + + expect( loggedEvents ).to.deep.equal( [ + 'insert:simpleBlock:0:1', + 'attribute:toStyle:display:block:simpleBlock:0:1', + 'insert:paragraph:0,0:0,1', + 'insert:$text:foo:0,0,0:0,0,3' + ] ); + } ); } ); } ); @@ -850,7 +942,7 @@ describe( 'DowncastHelpers', () => { } ); } ); - describe( 'with simple block view structure (without slots, without reconvert on children list change)', () => { + describe( 'with simple block view structure (without slots)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -1014,39 +1106,9 @@ describe( 'DowncastHelpers', () => { expect( viewAfter ).to.equal( viewBefore ); } ); - - it( 'should not reconvert on child element added', () => { - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'simpleBlock' - } ); - - downcastHelpers.elementToElement( { - model: 'paragraph', - view: 'p' - } ); - - setModelData( model, '' ); - - controller.downcastDispatcher.on( 'insert', ( evt, data ) => { - expect( data ).to.not.have.property( 'reconversion' ); - } ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '

' ); - - expect( viewAfter ).to.equal( viewBefore ); - } ); } ); - describe( 'with simple block view structure (with slots, without reconvert on children list change)', () => { + describe( 'with simple block view structure (with slots, changing attributes)', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', @@ -1190,26 +1252,6 @@ describe( 'DowncastHelpers', () => { expect( textAfter, 'text' ).to.equal( textBefore ); } ); - it( 'should not reconvert on child element added', () => { - setModelData( model, '' ); - - controller.downcastDispatcher.on( 'insert', ( evt, data ) => { - expect( data ).to.not.have.property( 'reconversion' ); - } ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '

' ); - - expect( viewAfter ).to.equal( viewBefore ); - } ); - it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { setModelData( model, 'foo' ); @@ -1238,10 +1280,7 @@ describe( 'DowncastHelpers', () => { } ); downcastHelpers.elementToStructure( { - model: { - name: 'simpleBlock', - children: true - }, + model: 'simpleBlock', view: ( modelElement, { writer, slotFor } ) => { const viewElement = writer.createContainerElement( 'div', getViewAttributes( modelElement ) ); @@ -1793,6 +1832,39 @@ describe( 'DowncastHelpers', () => { expect( innerDivAfter, 'inner div' ).to.equal( innerDivBefore ); expect( spy.notCalled ).to.be.true; } ); + + it( 'should fire conversion events in proper order', () => { + const loggedEvents = []; + + controller.downcastDispatcher.on( 'insert', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const log = 'insert:' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + }, { priority: 'highest' } ); + + controller.downcastDispatcher.on( 'attribute', ( evt, data ) => { + const itemId = data.item.name ? data.item.name : '$text:' + data.item.data; + const key = data.attributeKey; + const value = data.attributeNewValue; + const log = 'attribute:' + key + ':' + value + ':' + itemId + ':' + data.range.start.path + ':' + data.range.end.path; + + loggedEvents.push( log ); + }, { priority: 'highest' } ); + + setModelData( model, 'foobar' ); + + expectResult( '

foo

bar

' ); + + expect( loggedEvents ).to.deep.equal( [ + 'insert:complex:0:1', + 'attribute:toClass:true:complex:0:1', + 'insert:paragraph:0,0:0,1', + 'insert:$text:foo:0,0,0:0,0,3', + 'insert:paragraph:0,1:0,2', + 'insert:$text:bar:0,1,0:0,1,3' + ] ); + } ); } ); describe( 'with complex view structure - multiple slots', () => { From 56e1cf24ca44cf8e35f3b1b449540e77b3750821 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 7 Oct 2021 18:28:33 +0200 Subject: [PATCH 073/140] Updated tests. --- packages/ckeditor5-table/src/tableediting.js | 6 +- .../tests/converters/downcast.js | 601 ++++++++++-------- .../tests/tablecaption/tablecaptionediting.js | 21 +- 3 files changed, 351 insertions(+), 277 deletions(-) diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index bc2153e09bc..f4b26abab43 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -93,16 +93,14 @@ export default class TableEditing extends Plugin { conversion.for( 'editingDowncast' ).elementToStructure( { model: { name: 'table', - attributes: [ 'headingRows' ], - children: true + attributes: [ 'headingRows' ] }, view: downcastTable( tableUtils, { asWidget: true } ) } ); conversion.for( 'dataDowncast' ).elementToStructure( { model: { name: 'table', - attributes: [ 'headingRows' ], - children: true + attributes: [ 'headingRows' ] }, view: downcastTable( tableUtils ) } ); diff --git a/packages/ckeditor5-table/tests/converters/downcast.js b/packages/ckeditor5-table/tests/converters/downcast.js index 7e8b2d951ee..6c0a7b0e9cd 100644 --- a/packages/ckeditor5-table/tests/converters/downcast.js +++ b/packages/ckeditor5-table/tests/converters/downcast.js @@ -7,6 +7,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import { toWidgetEditable } from '@ckeditor/ckeditor5-widget'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; @@ -15,7 +16,7 @@ import { modelTable, viewTable } from '../_utils/utils'; import TableEditing from '../../src/tableediting'; describe( 'downcast converters', () => { - let editor, model, root, view; + let editor, model, root, view, viewRoot; testUtils.createSinonSandbox(); @@ -25,13 +26,14 @@ describe( 'downcast converters', () => { model = editor.model; root = model.document.getRoot( 'main' ); view = editor.editing.view; + viewRoot = view.document.getRoot(); } ); afterEach( () => { return editor.destroy(); } ); - describe( 'downcastInsertTable()', () => { + describe( 'downcastTable()', () => { describe( 'editing pipeline', () => { it( 'should create table as a widget', () => { setModelData( model, modelTable( [ [ '' ] ] ) ); @@ -51,6 +53,80 @@ describe( 'downcast converters', () => { '' ); } ); + + it( 'should reconvert table on headingRows attribute change', () => { + setModelData( model, modelTable( [ + [ '00' ], + [ '10' ] + ] ) ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '00' + + '
' + + '10' + + '
' + + '' + ); + + const viewFigureBefore = viewRoot.getChild( 0 ); + const viewTableBefore = viewFigureBefore.getChild( 1 ); + const viewTableRow0Before = viewTableBefore.getChild( 0 ).getChild( 0 ); + const viewTableRow1Before = viewTableBefore.getChild( 0 ).getChild( 1 ); + const viewTableCell0Before = viewTableRow0Before.getChild( 0 ); + const viewTableCell1Before = viewTableRow1Before.getChild( 1 ); + + model.change( writer => { + writer.setAttribute( 'headingRows', 1, root.getChild( 0 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '00' + + '
' + + '10' + + '
' + + '
' + ); + + const viewFigureAfter = viewRoot.getChild( 0 ); + const viewTableAfter = viewFigureAfter.getChild( 1 ); + const viewTableRow0After = viewTableAfter.getChild( 0 ).getChild( 0 ); + const viewTableRow1After = viewTableAfter.getChild( 1 ).getChild( 0 ); + const viewTableCell0After = viewTableRow0After.getChild( 0 ); + const viewTableCell1After = viewTableRow1After.getChild( 1 ); + + expect( viewFigureAfter ).to.not.equal( viewFigureBefore ); + expect( viewTableAfter ).to.not.equal( viewTableBefore ); + expect( viewTableRow0After ).to.equal( viewTableRow0Before ); + expect( viewTableCell0After ).to.not.equal( viewTableCell0Before ); + expect( viewTableRow1After ).to.equal( viewTableRow1Before ); + expect( viewTableCell1After ).to.equal( viewTableCell1Before ); + } ); } ); describe( 'data pipeline', () => { @@ -197,6 +273,7 @@ describe( 'downcast converters', () => { it( 'should be possible to overwrite', () => { editor.conversion.elementToElement( { model: 'tableRow', view: 'tr', converterPriority: 'high' } ); editor.conversion.elementToElement( { model: 'tableCell', view: 'td', converterPriority: 'high' } ); + editor.conversion.elementToElement( { model: 'paragraph', view: 'p', converterPriority: 'highest' } ); editor.conversion.for( 'downcast' ).add( dispatcher => { dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => { conversionApi.consumable.consume( data.item, 'insert' ); @@ -347,8 +424,7 @@ describe( 'downcast converters', () => { } ); } ); - describe( 'downcastInsertRow()', () => { - // The insert row downcast conversion is not executed in data pipeline. + describe( 'downcastRow()', () => { describe( 'editing pipeline', () => { it( 'should react to changed rows', () => { setModelData( model, modelTable( [ @@ -473,12 +549,12 @@ describe( 'downcast converters', () => { model.change( writer => { const row = writer.createElement( 'tableRow' ); - writer.setAttribute( 'headingRows', 3, table ); - writer.insert( row, table, 1 ); writer.insertElement( 'tableCell', row, 'end' ); writer.insertElement( 'tableCell', row, 'end' ); + + writer.setAttribute( 'headingRows', 3, table ); } ); assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [ @@ -559,16 +635,254 @@ describe( 'downcast converters', () => { '' + '' + '' + - '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '00' + + '
' + + '' + + '
' + + '' + ); + } ); + + it( 'should react to removed row from the beginning of a body rows (no heading rows)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ] ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + writer.remove( table.getChild( 1 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '00' + + '' + + '01' + + '
' + + '
' + ); + } ); + + it( 'should react to removed row from the end of a body rows (no heading rows)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ] ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + writer.remove( table.getChild( 0 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '10' + + '' + + '11' + + '
' + + '
' + ); + } ); + + it( 'should react to removed row from the beginning of a heading rows (no body rows)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { headingRows: 2 } ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + // Removing row from a heading section changes requires changing heading rows attribute. + writer.setAttribute( 'headingRows', 1, table ); + writer.remove( table.getChild( 0 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '10' + + '' + + '11' + + '
' + + '
' + ); + } ); + + it( 'should react to removed row from the end of a heading rows (no body rows)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { headingRows: 2 } ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + // Removing row from a heading section changes requires changing heading rows attribute. + writer.setAttribute( 'headingRows', 1, table ); + writer.remove( table.getChild( 1 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '00' + + '' + + '01' + + '
' + + '
' + ); + } ); + + it( 'should react to removed row from the end of a heading rows (first cell in body has colspan)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01', '02', '03' ], + [ { rowspan: 2, colspan: 2, contents: '10' }, '12', '13' ], + [ '22', '23' ] + ], { headingRows: 1 } ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + // Removing row from a heading section changes requires changing heading rows attribute. + writer.remove( table.getChild( 0 ) ); + writer.setAttribute( 'headingRows', 0, table ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '10' + + '' + + '12' + + '' + + '13' + + '
' + + '22' + + '' + + '23' + + '
' + + '
' + ); + } ); + + it( 'should remove empty thead if a last row was removed from a heading rows (has heading and body)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { headingRows: 1 } ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + // Removing row from a heading section changes requires changing heading rows attribute. + writer.removeAttribute( 'headingRows', table ); + writer.remove( table.getChild( 0 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '10' + + '' + + '11' + + '
' + + '
' + ); + } ); + + it( 'should remove empty tbody if a last row was removed a body rows (has heading and body)', () => { + setModelData( model, modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { headingRows: 1 } ) ); + + const table = root.getChild( 0 ); + + model.change( writer => { + writer.remove( table.getChild( 1 ) ); + } ); + + assertEqualMarkup( getViewData( view, { withoutSelection: true } ), + '
' + + '
' + + '' + + '' + + '' + + '' + - '' + - '' + + '' + + '' + '' + - '' + + '' + '
' + '00' + - '' + - '
' + - '' + - '' + + '01' + + '
' + '
' ); @@ -576,8 +890,7 @@ describe( 'downcast converters', () => { } ); } ); - describe( 'downcastInsertCell()', () => { - // The insert table cell downcast conversion is not executed in data pipeline. + describe( 'downcastCell()', () => { describe( 'editing pipeline', () => { it( 'should add tableCell on proper index in tr', () => { setModelData( model, modelTable( [ @@ -709,8 +1022,7 @@ describe( 'downcast converters', () => { } ); } ); - describe( 'downcastTableHeadingColumnsChange()', () => { - // The heading columns change downcast conversion is not executed in data pipeline. + describe( 'heading columns conversion', () => { describe( 'editing pipeline', () => { it( 'should work for adding heading columns', () => { setModelData( model, modelTable( [ @@ -785,6 +1097,11 @@ describe( 'downcast converters', () => { it( 'should be possible to overwrite', () => { editor.conversion.attributeToAttribute( { model: 'headingColumns', view: 'headingColumns', converterPriority: 'high' } ); + editor.conversion.elementToElement( { + model: 'tableCell', + view: ( tableCell, { writer } ) => toWidgetEditable( writer.createEditableElement( 'td' ), writer ), + converterPriority: 'high' + } ); setModelData( model, modelTable( [ [ '00[] ' ] ] ) ); const table = root.getChild( 0 ); @@ -890,10 +1207,7 @@ describe( 'downcast converters', () => { } ); } ); - describe( 'downcastTableHeadingRowsChange', () => { - // The heading rows change downcast conversion is not executed in data pipeline. - // Note that headingRows table attribute triggers whole table downcast. - + describe( 'heading rows conversion', () => { describe( 'editing pipeline', () => { it( 'should work for adding heading rows', () => { setModelData( model, modelTable( [ @@ -1208,249 +1522,6 @@ describe( 'downcast converters', () => { } ); } ); - describe( 'downcastRemoveRow()', () => { - // The remove row downcast conversion is not executed in data pipeline. - describe( 'editing pipeline', () => { - it( 'should react to removed row from the beginning of a body rows (no heading rows)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ] ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - writer.remove( table.getChild( 1 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '00' + - '' + - '01' + - '
' + - '
' - ); - } ); - - it( 'should react to removed row from the end of a body rows (no heading rows)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ] ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - writer.remove( table.getChild( 0 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '10' + - '' + - '11' + - '
' + - '
' - ); - } ); - - it( 'should react to removed row from the beginning of a heading rows (no body rows)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ], { headingRows: 2 } ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - // Removing row from a heading section changes requires changing heading rows attribute. - writer.setAttribute( 'headingRows', 1, table ); - writer.remove( table.getChild( 0 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '10' + - '' + - '11' + - '
' + - '
' - ); - } ); - - it( 'should react to removed row from the end of a heading rows (no body rows)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ], { headingRows: 2 } ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - // Removing row from a heading section changes requires changing heading rows attribute. - writer.setAttribute( 'headingRows', 1, table ); - writer.remove( table.getChild( 1 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '00' + - '' + - '01' + - '
' + - '
' - ); - } ); - - it( 'should react to removed row from the end of a heading rows (first cell in body has colspan)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01', '02', '03' ], - [ { rowspan: 2, colspan: 2, contents: '10' }, '12', '13' ], - [ '22', '23' ] - ], { headingRows: 1 } ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - // Removing row from a heading section changes requires changing heading rows attribute. - writer.remove( table.getChild( 0 ) ); - writer.setAttribute( 'headingRows', 0, table ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '10' + - '' + - '12' + - '' + - '13' + - '
' + - '22' + - '' + - '23' + - '
' + - '
' - ); - } ); - - it( 'should remove empty thead if a last row was removed from a heading rows (has heading and body)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ], { headingRows: 1 } ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - // Removing row from a heading section changes requires changing heading rows attribute. - writer.removeAttribute( 'headingRows', table ); - writer.remove( table.getChild( 0 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '10' + - '' + - '11' + - '
' + - '
' - ); - } ); - - it( 'should remove empty tbody if a last row was removed a body rows (has heading and body)', () => { - setModelData( model, modelTable( [ - [ '00[]', '01' ], - [ '10', '11' ] - ], { headingRows: 1 } ) ); - - const table = root.getChild( 0 ); - - model.change( writer => { - writer.remove( table.getChild( 1 ) ); - } ); - - assertEqualMarkup( getViewData( view, { withoutSelection: true } ), - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '00' + - '' + - '01' + - '
' + - '
' - ); - } ); - } ); - } ); - describe( 'marker highlight conversion on table cell', () => { describe( 'single class in highlight descriptor', () => { beforeEach( async () => { diff --git a/packages/ckeditor5-table/tests/tablecaption/tablecaptionediting.js b/packages/ckeditor5-table/tests/tablecaption/tablecaptionediting.js index 6cc9cef9af8..fc11edf84c5 100644 --- a/packages/ckeditor5-table/tests/tablecaption/tablecaptionediting.js +++ b/packages/ckeditor5-table/tests/tablecaption/tablecaptionediting.js @@ -26,11 +26,18 @@ describe( 'TableCaptionEditing', () => { isBlock: true, allowWhere: '$block' } ); - schema.register( 'caption', { - allowIn: 'foo', - allowContentOf: '$block', - isLimit: true - } ); + + if ( schema.isRegistered( 'caption' ) ) { + schema.extend( 'caption', { + allowIn: 'foo' + } ); + } else { + schema.register( 'caption', { + allowIn: 'foo', + allowContentOf: '$block', + isLimit: true + } ); + } conversion.elementToElement( { view: 'foo', @@ -87,9 +94,7 @@ describe( 'TableCaptionEditing', () => { it( 'should not convert caption outside of the table', async () => { const editor = await VirtualTestEditor .create( { - plugins: [ - FakePlugin, - TableEditing, TableCaptionEditing, Paragraph, TableCaptionEditing ] + plugins: [ TableEditing, TableCaptionEditing, Paragraph, TableCaptionEditing, FakePlugin ] } ); setModelData( editor.model, From e1d307c429570fe051ff6155abc5b8091bdbcd08 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 8 Oct 2021 12:40:44 +0200 Subject: [PATCH 074/140] Added comments. --- .../src/conversion/downcasthelpers.js | 7 +- .../src/converters/downcast.js | 54 +++++++------- .../converters/table-cell-refresh-handler.js | 6 +- .../table-headings-refresh-handler.js | 74 ++++++++----------- .../tests/converters/downcast.js | 2 +- ...fixer.js => table-cell-refresh-handler.js} | 2 +- 6 files changed, 64 insertions(+), 81 deletions(-) rename packages/ckeditor5-table/tests/converters/{table-cell-refresh-post-fixer.js => table-cell-refresh-handler.js} (99%) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 21af8442f58..cf4bcc3ad76 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -238,8 +238,7 @@ export default class DowncastHelpers extends ConversionHelpers { * editor.conversion.for( 'downcast' ).elementToStructure( { * model: { * name: 'table', - * attributes: [ 'headingRows' ], - * children: true + * attributes: [ 'headingRows' ] * }, * view: ( modelElement, conversionApi ) => { * const { writer, slotFor } = conversionApi; @@ -289,8 +288,6 @@ export default class DowncastHelpers extends ConversionHelpers { * @param {String} [config.model.name] The name of the model element to convert. * @param {String|Array.} [config.model.attributes] The list of attribute names that should be consumed while creating * the view structure. Note that the view will be reconverted if any of the listed attributes will change. - * @param {Boolean} [config.model.children] Specifies whether the view structure requires reconversion if the list - * of model child nodes changed. * @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function * that takes the model element and {@link module:engine/conversion/downcasthelpers~DowncastConversionWithSlotsApi downcast * conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into. @@ -1055,6 +1052,7 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { // Convert attributes before converting children. conversionApi.convertAttributes( data.item ); + // Convert children or reinsert previous view elements. reinsertOrConvertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } ); }; } @@ -1687,7 +1685,6 @@ function downcastElementToElement( config ) { // @param {String|Object} config.model // @param {String} [config.model.name] // @param {Array.} [config.model.attributes] -// @param {Boolean} [config.model.children] // @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view // @returns {Function} Conversion helper. function downcastElementToStructure( config ) { diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index f3c07c696a1..d4f2563edf6 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -11,7 +11,12 @@ import TableWalker from './../tablewalker'; import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget'; /** - * TODO + * Model table element to view table element conversion helper. + * + * @param {module:table/tableutils~TableUtils} tableUtils The `TableUtils` plugin instance. + * @param {Object} options + * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @returns {Function} Element creator. */ export function downcastTable( tableUtils, options = {} ) { return ( table, { writer, slotFor } ) => { @@ -54,7 +59,9 @@ export function downcastTable( tableUtils, options = {} ) { } /** - * TODO + * Model table row element to view `` element conversion helper. + * + * @returns {Function} Element creator. */ export function downcastRow() { return ( tableRow, { writer } ) => { @@ -65,7 +72,14 @@ export function downcastRow() { } /** - * TODO + * Model table cell element to view `` or `` element conversion helper. + * + * This conversion helper will create proper `` elements for table cells that are in the heading section (heading row or column) + * and `` otherwise. + * + * @param {Object} options + * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @returns {Function} Element creator. */ export function downcastCell( options = {} ) { return ( tableCell, { writer } ) => { @@ -74,16 +88,14 @@ export function downcastCell( options = {} ) { const rowIndex = table.getChildIndex( tableRow ); const tableWalker = new TableWalker( table, { row: rowIndex } ); - - const tableAttributes = { - headingRows: table.getAttribute( 'headingRows' ) || 0, - headingColumns: table.getAttribute( 'headingColumns' ) || 0 - }; + const headingRows = table.getAttribute( 'headingRows' ) || 0; + const headingColumns = table.getAttribute( 'headingColumns' ) || 0; // We need to iterate over a table in order to get proper row & column values from a walker. for ( const tableSlot of tableWalker ) { - if ( tableSlot.cell === tableCell ) { - const cellElementName = getCellElementName( tableSlot, tableAttributes ); + if ( tableSlot.cell == tableCell ) { + const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns; + const cellElementName = isHeading ? 'th' : 'td'; return options.asWidget ? toWidgetEditable( writer.createEditableElement( cellElementName ), writer ) : @@ -97,12 +109,14 @@ export function downcastCell( options = {} ) { * Overrides paragraph inside table cell conversion. * * This converter: - * * should be used to override default paragraph conversion in the editing view. - * * It will only convert placed directly inside . + * * should be used to override default paragraph conversion. + * * It will only convert `` placed directly inside ``. * * For a single paragraph without attributes it returns `` to simulate data table. * * For all other cases it returns `

` element. * - * TODO + * @param {Object} options + * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @returns {Function} Element creator. */ export function convertParagraphInTableCell( options = {} ) { return ( modelElement, { writer, consumable, mapper } ) => { @@ -138,7 +152,7 @@ export function convertParagraphInTableCell( options = {} ) { export function isSingleParagraphWithoutAttributes( modelElement ) { const tableCell = modelElement.parent; - const isSingleParagraph = tableCell.childCount === 1; + const isSingleParagraph = tableCell.childCount == 1; return isSingleParagraph && !hasAnyAttribute( modelElement ); } @@ -157,18 +171,6 @@ function toTableWidget( viewElement, writer ) { return toWidget( viewElement, writer, { hasSelectionHandle: true } ); } -// Returns `th` for heading cells and `td` for other cells for the current table walker value. -// -// @param {module:table/tablewalker~TableSlot} tableSlot -// @param {{headingColumns, headingRows}} tableAttributes -// @returns {String} -function getCellElementName( { row, column }, { headingColumns, headingRows } ) { - const isColumnHeading = headingRows && row < headingRows; - const isRowHeading = headingColumns && column < headingColumns; - - return isColumnHeading || isRowHeading ? 'th' : 'td'; -} - // Checks if an element has any attributes set. // // @param {module:engine/model/element~Element element diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js index 9a82b4c1938..b0319cbd3a0 100644 --- a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js @@ -10,9 +10,7 @@ import { isSingleParagraphWithoutAttributes } from './downcast'; /** - * TODO - * - * Injects a table cell post-fixer into the model which marks the table cell in the differ to have it re-rendered. + * A table cell refresh handler which marks the table cell in the differ to have it re-rendered. * * Model `paragraph` inside a table cell can be rendered as `` or `

`. It is rendered as `` if this is the only block * element in that table cell and it does not have any attributes. It is rendered as `

` otherwise. @@ -38,7 +36,7 @@ export default function tableCellRefreshHandler( model, mapper ) { } for ( const tableCell of cellsToCheck.values() ) { - for ( const paragraph of [ ...tableCell.getChildren() ].filter( child => shouldRefresh( child, mapper ) ) ) { + for ( const paragraph of Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, mapper ) ) ) { differ.refreshItem( paragraph ); } } diff --git a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js index 8edfb475951..ccfe8e99574 100644 --- a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js @@ -10,22 +10,23 @@ import TableWalker from '../tablewalker'; /** - * TODO + * A table headings refresh handler which marks the table cells or rows in the differ to have it re-rendered + * if the headings attribute changed. * - * Injects a table post-fixer into the model which marks the table in the differ to have it re-rendered. + * Table heading rows and heading columns are represented in the model by a `headingRows` and `headingColumns` attributes. * - * Table heading rows are represented in the model by a `headingRows` attribute. However, in the view, it's represented as separate - * sections of the table (`` or ``) and changing `headingRows` attribute requires moving table rows between two sections. - * This causes problems with structural changes in a table (like adding and removing rows) thus atomic converters cannot be used. - * - * When table `headingRows` attribute changes, the entire table is re-rendered. + * When table headings attribute changes, all the cells/rows are marked to re-render to change between `` and ``. * * @param {module:engine/model/model~Model} model + * @param {module:engine/conversion/mapper~Mapper} mapper */ export default function tableHeadingsRefreshHandler( model, mapper ) { const differ = model.document.differ; for ( const change of differ.getChanges() ) { + let table; + let isRowChange = false; + if ( change.type == 'attribute' ) { const element = change.range.start.nodeAfter; @@ -33,50 +34,35 @@ export default function tableHeadingsRefreshHandler( model, mapper ) { continue; } - if ( change.attributeKey == 'headingRows' ) { - reconvertOnAttributeChange( differ, change, element, 'startRow', 'endRow' ); - } else if ( change.attributeKey == 'headingColumns' ) { - reconvertOnAttributeChange( differ, change, element, 'startColumn', 'endColumn' ); + if ( change.attributeKey != 'headingRows' && change.attributeKey != 'headingColumns' ) { + continue; } - } else { - /* istanbul ignore else */ - if ( change.type == 'insert' || change.type == 'remove' ) { - if ( change.name != 'tableRow' && change.name != 'tableCell' ) { - continue; - } - const table = change.position.findAncestor( 'table' ); - const headingRows = table.getAttribute( 'headingRows' ) || 0; - const headingColumns = table.getAttribute( 'headingColumns' ) || 0; - - const tableWalker = new TableWalker( table ); + table = element; + isRowChange = change.attributeKey == 'headingRows'; + } else if ( change.name == 'tableRow' || change.name == 'tableCell' ) { + table = change.position.findAncestor( 'table' ); + isRowChange = change.name == 'tableRow'; + } - for ( const tableSlot of tableWalker ) { - const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns; - const expectedElementName = isHeading ? 'th' : 'td'; + if ( !table ) { + continue; + } - const viewElement = mapper.toViewElement( tableSlot.cell ); + const headingRows = table.getAttribute( 'headingRows' ) || 0; + const headingColumns = table.getAttribute( 'headingColumns' ) || 0; - if ( viewElement && viewElement.is( 'element' ) && viewElement.name != expectedElementName ) { - differ.refreshItem( change.name == 'tableRow' ? tableSlot.cell.parent : tableSlot.cell ); - } - } - } - } - } -} + const tableWalker = new TableWalker( table ); -// TODO -function reconvertOnAttributeChange( differ, change, table, startOption, endOption ) { - const oldHeadings = change.attributeOldValue || 0; - const newHeadings = change.attributeNewValue || 0; + for ( const tableSlot of tableWalker ) { + const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns; + const expectedElementName = isHeading ? 'th' : 'td'; - const tableWalker = new TableWalker( table, { - [ startOption ]: Math.min( oldHeadings, newHeadings ), - [ endOption ]: Math.max( oldHeadings, newHeadings ) - 1 - } ); + const viewElement = mapper.toViewElement( tableSlot.cell ); - for ( const tableSlot of tableWalker ) { - differ.refreshItem( tableSlot.cell ); + if ( viewElement && viewElement.is( 'element' ) && viewElement.name != expectedElementName ) { + differ.refreshItem( isRowChange ? tableSlot.cell.parent : tableSlot.cell ); + } + } } } diff --git a/packages/ckeditor5-table/tests/converters/downcast.js b/packages/ckeditor5-table/tests/converters/downcast.js index 6c0a7b0e9cd..4ff3b6f3ba6 100644 --- a/packages/ckeditor5-table/tests/converters/downcast.js +++ b/packages/ckeditor5-table/tests/converters/downcast.js @@ -122,7 +122,7 @@ describe( 'downcast converters', () => { expect( viewFigureAfter ).to.not.equal( viewFigureBefore ); expect( viewTableAfter ).to.not.equal( viewTableBefore ); - expect( viewTableRow0After ).to.equal( viewTableRow0Before ); + expect( viewTableRow0After ).to.not.equal( viewTableRow0Before ); expect( viewTableCell0After ).to.not.equal( viewTableCell0Before ); expect( viewTableRow1After ).to.equal( viewTableRow1Before ); expect( viewTableCell1After ).to.equal( viewTableCell1Before ); diff --git a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js b/packages/ckeditor5-table/tests/converters/table-cell-refresh-handler.js similarity index 99% rename from packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js rename to packages/ckeditor5-table/tests/converters/table-cell-refresh-handler.js index 9481853d89f..7a339f08cf9 100644 --- a/packages/ckeditor5-table/tests/converters/table-cell-refresh-post-fixer.js +++ b/packages/ckeditor5-table/tests/converters/table-cell-refresh-handler.js @@ -15,7 +15,7 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import TableEditing from '../../src/tableediting'; import { viewTable } from '../_utils/utils'; -describe( 'Table cell refresh post-fixer', () => { +describe( 'Table cell refresh handler', () => { let editor, model, doc, root, view, element; testUtils.createSinonSandbox(); From 688304c566129a65ed574ff71c84225259a6191b Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 8 Oct 2021 16:36:24 +0200 Subject: [PATCH 075/140] Review changes. --- .../src/conversion/downcastdispatcher.js | 6 +++--- packages/ckeditor5-engine/src/model/differ.js | 2 +- .../tests/conversion/downcasthelpers.js | 2 +- packages/ckeditor5-table/src/converters/downcast.js | 12 ++++++------ .../src/converters/table-cell-refresh-handler.js | 4 +++- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js index b8cd8594a1c..032d962001b 100644 --- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js +++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js @@ -579,12 +579,12 @@ export default class DowncastDispatcher { * * @private * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document. - * @param {Set.} [invalidatedMappings] A set of model elements that should not reuse their + * @param {Set.} [refreshedItems] A set of model elements that should not reuse their * previous view representations. * @param {Object} [options] Optional options passed to `convertionApi.options`. * @return {module:engine/conversion/downcastdispatcher~DowncastConversionApi} The conversion API object. */ - _createConversionApi( writer, invalidatedMappings = new Set(), options = {} ) { + _createConversionApi( writer, refreshedItems = new Set(), options = {} ) { const conversionApi = { ...this._conversionApi, consumable: new Consumable(), @@ -593,7 +593,7 @@ export default class DowncastDispatcher { convertItem: item => this._convertInsert( Range._createOn( item ), conversionApi ), convertChildren: element => this._convertInsert( Range._createIn( element ), conversionApi, { doNotAddConsumables: true } ), convertAttributes: item => this._testAndFireAddAttributes( item, conversionApi ), - canReuseView: viewElement => !invalidatedMappings.has( conversionApi.mapper.toModelElement( viewElement ) ) + canReuseView: viewElement => !refreshedItems.has( conversionApi.mapper.toModelElement( viewElement ) ) }; this._firedEventsMap.set( conversionApi, new Map() ); diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 5ec163e86b8..33f157d9d77 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -100,7 +100,7 @@ export default class Differ { this._cachedChangesWithGraveyard = null; /** - * Set of model items that were marked to get refreshed. + * Set of model items that were marked to get refreshed in {@link #refreshItem}. * * @private * @type {Set.} diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 11e1ee3bab2..3992fa109f0 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -137,7 +137,7 @@ describe( 'DowncastHelpers', () => { expectResult( '' ); } ); - describe( 'converting element itself', () => { + describe( 'converting element', () => { beforeEach( () => { model.schema.register( 'simpleBlock', { allowIn: '$root', diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index d4f2563edf6..dd1f668dd49 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -14,8 +14,8 @@ import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget'; * Model table element to view table element conversion helper. * * @param {module:table/tableutils~TableUtils} tableUtils The `TableUtils` plugin instance. - * @param {Object} options - * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @param {Object} [options] + * @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget. * @returns {Function} Element creator. */ export function downcastTable( tableUtils, options = {} ) { @@ -77,8 +77,8 @@ export function downcastRow() { * This conversion helper will create proper `` elements for table cells that are in the heading section (heading row or column) * and `` otherwise. * - * @param {Object} options - * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @param {Object} [options] + * @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget. * @returns {Function} Element creator. */ export function downcastCell( options = {} ) { @@ -114,8 +114,8 @@ export function downcastCell( options = {} ) { * * For a single paragraph without attributes it returns `` to simulate data table. * * For all other cases it returns `

` element. * - * @param {Object} options - * @param {Boolean} options.asWidget If set to `true`, the downcast conversion will produce a widget. + * @param {Object} [options] + * @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget. * @returns {Function} Element creator. */ export function convertParagraphInTableCell( options = {} ) { diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js index b0319cbd3a0..9d79b7e7442 100644 --- a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js @@ -36,7 +36,9 @@ export default function tableCellRefreshHandler( model, mapper ) { } for ( const tableCell of cellsToCheck.values() ) { - for ( const paragraph of Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, mapper ) ) ) { + const paragraphsToRefresh = Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, mapper ) ); + + for ( const paragraph of paragraphsToRefresh ) { differ.refreshItem( paragraph ); } } From 26cb738dc5ce26605a1a7078f54fb0db0a9eefe3 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Tue, 12 Oct 2021 11:49:52 +0200 Subject: [PATCH 076/140] Cleanup. --- .../tests/manual/classiceditor.js | 39 ++++++++++++++++++- .../src/conversion/downcasthelpers.js | 20 ++++++++++ .../tests/manual/element-reconversion.js | 5 +++ .../src/horizontallineediting.js | 2 +- .../ckeditor5-html-support/src/datafilter.js | 2 +- .../src/image/imageblockediting.js | 12 +++--- .../src/image/imageinlineediting.js | 6 +-- packages/ckeditor5-image/src/image/utils.js | 18 +++++++-- .../ckeditor5-image/tests/image/converters.js | 18 +++++---- packages/ckeditor5-image/tests/image/utils.js | 20 +++++++--- packages/ckeditor5-link/tests/linkui.js | 2 +- .../src/mediaembedediting.js | 14 +++---- packages/ckeditor5-media-embed/src/utils.js | 3 +- .../tests/automediaembed.js | 6 +-- .../src/pagebreakediting.js | 4 +- .../ckeditor5-widget/tests/widget-events.js | 4 +- packages/ckeditor5-widget/tests/widget.js | 5 ++- .../ckeditor5-widget/tests/widgetresize.js | 2 +- .../widgettypearound/widgettypearound.js | 10 +++-- 19 files changed, 141 insertions(+), 51 deletions(-) diff --git a/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js b/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js index 74ec25ac495..d5eccd9d366 100644 --- a/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js +++ b/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js @@ -14,13 +14,50 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'; import { createObserver } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; +import { Command, Plugin } from '@ckeditor/ckeditor5-core'; let editor, editable, observer; +class MyCommand extends Command { + execute() { + this.editor.model.change( writer => { + // Insert * at the current selection position + // in a way that will result in creating a valid model structure. + this.editor.model.insertContent( writer.createElement( 'el' ) ); + } ); + } + + refresh() { + this.isEnabled = true; + } +} + +class TestPlugin extends Plugin { + init() { + this.editor.commands.add( 'myCommand', new MyCommand( this.editor ) ); + + this.editor.model.schema.register( 'el', { + inheritAllFrom: '$block' + } ); + + this.editor.conversion.for( 'downcast' ).elementToElement( { + model: 'el', + view: ( modelElement, { writer } ) => { + const section = writer.createContainerElement( 'section' ); + const img = writer.createEmptyElement( 'img' ); + + writer.insert( writer.createPositionAt( section, 0 ), img ); + + return section; + } + } ); + } +} + function initEditor() { ClassicEditor .create( document.querySelector( '#editor' ), { - plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic ], + plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic, TestPlugin ], toolbar: [ 'heading', '|', 'bold', 'italic', 'undo', 'redo' ] } ) .then( newEditor => { diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 3db1f388a77..3d42f8c384f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -21,6 +21,7 @@ import ConversionHelpers from './conversionhelpers'; import { cloneDeep } from 'lodash-es'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import toArray from '@ckeditor/ckeditor5-utils/src/toarray'; +import { logWarning } from '@ckeditor/ckeditor5-utils'; /** * Downcast conversion helper functions. @@ -1044,6 +1045,25 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { return; } + const hasContent = Array.from( viewElement.getChildren() ) + .some( element => !element.is( 'uiElement' ) ); + + if ( hasContent ) { + /** + * The Easy Image feature requires one of the following plugins to be loaded to work correctly: + * + * * {@link module:image/imageblock~ImageBlock}, + * * {@link module:image/imageinline~ImageInline}, + * * {@link module:image/image~Image} (loads both `ImageBlock` and `ImageInline`) + * + * Please make sure your editor configuration is correct. + * + * @error easy-image-image-feature-missing + * @param {module:core/editor/editor~Editor} editor + */ + logWarning( 'element-to-element-used-with-multiple-elements', viewElement ); + } + // Consume an element insertion and all present attributes that are specified as a reconversion triggers. consumer( data.item, conversionApi.consumable ); diff --git a/packages/ckeditor5-engine/tests/manual/element-reconversion.js b/packages/ckeditor5-engine/tests/manual/element-reconversion.js index ccdffb131d4..525288790e4 100644 --- a/packages/ckeditor5-engine/tests/manual/element-reconversion.js +++ b/packages/ckeditor5-engine/tests/manual/element-reconversion.js @@ -47,6 +47,11 @@ function Items( editor ) { const attributes = { class: 'items ' }; if ( mode === 'threshold' ) { + writer.createEmptyElement( 'div', { + 'data-amount': getThreshold( modelElement.childCount ), + ...attributes + } ); + return writer.createContainerElement( 'div', { 'data-amount': getThreshold( modelElement.childCount ), ...attributes diff --git a/packages/ckeditor5-horizontal-line/src/horizontallineediting.js b/packages/ckeditor5-horizontal-line/src/horizontallineediting.js index c05a84ba495..7cf7524fb81 100644 --- a/packages/ckeditor5-horizontal-line/src/horizontallineediting.js +++ b/packages/ckeditor5-horizontal-line/src/horizontallineediting.js @@ -48,7 +48,7 @@ export default class HorizontalLineEditing extends Plugin { } } ); - conversion.for( 'editingDowncast' ).elementToElement( { + conversion.for( 'editingDowncast' ).elementToStructure( { model: 'horizontalLine', view: ( modelElement, { writer } ) => { const label = t( 'Horizontal line' ); diff --git a/packages/ckeditor5-html-support/src/datafilter.js b/packages/ckeditor5-html-support/src/datafilter.js index e08991caa46..d5ccd10aa45 100644 --- a/packages/ckeditor5-html-support/src/datafilter.js +++ b/packages/ckeditor5-html-support/src/datafilter.js @@ -331,7 +331,7 @@ export default class DataFilter extends Plugin { } ); conversion.for( 'upcast' ).add( viewToModelBlockAttributeConverter( definition, this ) ); - conversion.for( 'editingDowncast' ).elementToElement( { + conversion.for( 'editingDowncast' ).elementToStructure( { model: modelName, view: toObjectWidgetConverter( editor, definition ) } ); diff --git a/packages/ckeditor5-image/src/image/imageblockediting.js b/packages/ckeditor5-image/src/image/imageblockediting.js index 7bbbed94e85..15a8caa0ec5 100644 --- a/packages/ckeditor5-image/src/image/imageblockediting.js +++ b/packages/ckeditor5-image/src/image/imageblockediting.js @@ -22,7 +22,7 @@ import ImageTypeCommand from './imagetypecommand'; import ImageUtils from '../imageutils'; import { getImgViewElementMatcher, - createImageViewElement, + createBlockImageViewElement, determineImageTypeForInsertionAtSelection } from '../image/utils'; @@ -90,16 +90,16 @@ export default class ImageBlockEditing extends Plugin { const imageUtils = editor.plugins.get( 'ImageUtils' ); conversion.for( 'dataDowncast' ) - .elementToElement( { + .elementToStructure( { model: 'imageBlock', - view: ( modelElement, { writer } ) => createImageViewElement( writer, 'imageBlock' ) + view: ( modelElement, { writer, slotFor } ) => createBlockImageViewElement( writer, slotFor, 'imageBlock' ) } ); conversion.for( 'editingDowncast' ) - .elementToElement( { + .elementToStructure( { model: 'imageBlock', - view: ( modelElement, { writer } ) => imageUtils.toImageWidget( - createImageViewElement( writer, 'imageBlock' ), writer, t( 'image widget' ) + view: ( modelElement, { writer, slotFor } ) => imageUtils.toImageWidget( + createBlockImageViewElement( writer, slotFor, 'imageBlock' ), writer, t( 'image widget' ) ) } ); diff --git a/packages/ckeditor5-image/src/image/imageinlineediting.js b/packages/ckeditor5-image/src/image/imageinlineediting.js index a7492520ab1..b08c846de9f 100644 --- a/packages/ckeditor5-image/src/image/imageinlineediting.js +++ b/packages/ckeditor5-image/src/image/imageinlineediting.js @@ -21,7 +21,7 @@ import ImageTypeCommand from './imagetypecommand'; import ImageUtils from '../imageutils'; import { getImgViewElementMatcher, - createImageViewElement, + createInlineImageViewElement, determineImageTypeForInsertionAtSelection } from '../image/utils'; @@ -104,10 +104,10 @@ export default class ImageInlineEditing extends Plugin { } ); conversion.for( 'editingDowncast' ) - .elementToElement( { + .elementToStructure( { model: 'imageInline', view: ( modelElement, { writer } ) => imageUtils.toImageWidget( - createImageViewElement( writer, 'imageInline' ), writer, t( 'image widget' ) + createInlineImageViewElement( writer, 'imageInline' ), writer, t( 'image widget' ) ) } ); diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 9edff2fa6e1..9ca1f3de739 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -27,15 +27,25 @@ import { first } from 'ckeditor5/src/utils'; * @param {'imageBlock'|'imageInline'} imageType The type of created image. * @returns {module:engine/view/containerelement~ContainerElement} */ -export function createImageViewElement( writer, imageType ) { +export function createInlineImageViewElement( writer ) { const emptyElement = writer.createEmptyElement( 'img' ); + const container = writer.createContainerElement( 'span', { class: 'image-inline' }, { isAllowedInsideAttributeElement: true } ); - const container = imageType === 'imageBlock' ? - writer.createContainerElement( 'figure', { class: 'image' } ) : - writer.createContainerElement( 'span', { class: 'image-inline' }, { isAllowedInsideAttributeElement: true } ); + writer.insert( writer.createPositionAt( container, 0 ), emptyElement ); + + return container; +} + +export function createBlockImageViewElement( writer, slotFor = false ) { + const emptyElement = writer.createEmptyElement( 'img' ); + const container = writer.createContainerElement( 'figure', { class: 'image' } ); writer.insert( writer.createPositionAt( container, 0 ), emptyElement ); + if ( slotFor ) { + writer.insert( writer.createPositionAt( container, 1 ), slotFor( 'children' ) ); + } + return container; } diff --git a/packages/ckeditor5-image/tests/image/converters.js b/packages/ckeditor5-image/tests/image/converters.js index a1687d6d436..856e590e872 100644 --- a/packages/ckeditor5-image/tests/image/converters.js +++ b/packages/ckeditor5-image/tests/image/converters.js @@ -8,7 +8,10 @@ import { upcastImageFigure, downcastImageAttribute } from '../../src/image/converters'; -import { createImageViewElement } from '../../src/image/utils'; +import { + createBlockImageViewElement, + createInlineImageViewElement +} from '../../src/image/utils'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; @@ -46,18 +49,18 @@ describe( 'Image converters', () => { isInline: true } ); - const imageEditingElementCreator = ( modelElement, { writer } ) => - imageUtils.toImageWidget( createImageViewElement( writer, 'imageBlock' ), writer, '' ); + const imageEditingElementCreator = ( modelElement, { writer, slotFor } ) => + imageUtils.toImageWidget( createBlockImageViewElement( writer, slotFor ), writer, '' ); const imageInlineEditingElementCreator = ( modelElement, { writer } ) => - imageUtils.toImageWidget( createImageViewElement( writer, 'imageInline' ), writer, '' ); + imageUtils.toImageWidget( createInlineImageViewElement( writer ), writer, '' ); - editor.conversion.for( 'editingDowncast' ).elementToElement( { + editor.conversion.for( 'editingDowncast' ).elementToStructure( { model: 'imageBlock', view: imageEditingElementCreator } ); - editor.conversion.for( 'editingDowncast' ).elementToElement( { + editor.conversion.for( 'editingDowncast' ).elementToStructure( { model: 'imageInline', view: imageInlineEditingElementCreator } ); @@ -237,6 +240,7 @@ describe( 'Image converters', () => { describe( 'downcastImageAttribute', () => { it( 'should convert adding attribute to image', () => { setModelData( model, '' ); + const image = document.getRoot().getChild( 0 ); model.change( writer => { @@ -313,7 +317,7 @@ describe( 'Image converters', () => { } ); expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '

foo bar
' + '
foo bar
' ); } ); } ); diff --git a/packages/ckeditor5-image/tests/image/utils.js b/packages/ckeditor5-image/tests/image/utils.js index 8ac206eaca7..c34223a4338 100644 --- a/packages/ckeditor5-image/tests/image/utils.js +++ b/packages/ckeditor5-image/tests/image/utils.js @@ -25,7 +25,8 @@ import ImageUtils from '../../src/imageutils'; import { getImgViewElementMatcher, - createImageViewElement, + createBlockImageViewElement, + createInlineImageViewElement, determineImageTypeForInsertionAtSelection } from '../../src/image/utils'; @@ -280,7 +281,7 @@ describe( 'image utils', () => { } ); } ); - describe( 'createImageViewElement()', () => { + describe( 'createBlockImageViewElement()', () => { let writer; beforeEach( () => { @@ -289,16 +290,25 @@ describe( 'image utils', () => { } ); it( 'should create a figure element for "image" type', () => { - const element = createImageViewElement( writer, 'imageBlock' ); + const element = createBlockImageViewElement( writer ); expect( element.is( 'element', 'figure' ) ).to.be.true; expect( element.hasClass( 'image' ) ).to.be.true; expect( element.childCount ).to.equal( 1 ); expect( element.getChild( 0 ).is( 'emptyElement', 'img' ) ).to.be.true; } ); + } ); + + describe( 'createInlineImageViewElement()', () => { + let writer; + + beforeEach( () => { + const document = new ViewDocument( new StylesProcessor() ); + writer = new ViewDowncastWriter( document ); + } ); it( 'should create a span element for "imageInline" type', () => { - const element = createImageViewElement( writer, 'imageInline' ); + const element = createInlineImageViewElement( writer ); expect( element.is( 'element', 'span' ) ).to.be.true; expect( element.hasClass( 'image-inline' ) ).to.be.true; @@ -308,7 +318,7 @@ describe( 'image utils', () => { it( 'should create a span element for "imageInline" type that does not break the parent attribute element', () => { const paragraph = writer.createContainerElement( 'p' ); - const imageElement = createImageViewElement( writer, 'imageInline' ); + const imageElement = createInlineImageViewElement( writer ); const attributeElement = writer.createAttributeElement( 'a', { foo: 'bar' } ); writer.insert( writer.createPositionAt( paragraph, 0 ), imageElement ); diff --git a/packages/ckeditor5-link/tests/linkui.js b/packages/ckeditor5-link/tests/linkui.js index 7b8348598a7..e5169395f36 100644 --- a/packages/ckeditor5-link/tests/linkui.js +++ b/packages/ckeditor5-link/tests/linkui.js @@ -1115,7 +1115,7 @@ describe( 'LinkUI', () => { allowAttributesOf: '$text' } ); - editor.conversion.for( 'downcast' ).elementToElement( { + editor.conversion.for( 'downcast' ).elementToStructure( { model: 'inlineWidget', view: ( modelItem, { writer } ) => { const spanView = writer.createContainerElement( 'span', {}, { diff --git a/packages/ckeditor5-media-embed/src/mediaembedediting.js b/packages/ckeditor5-media-embed/src/mediaembedediting.js index 923e73a9ebb..5c0af7eead4 100644 --- a/packages/ckeditor5-media-embed/src/mediaembedediting.js +++ b/packages/ckeditor5-media-embed/src/mediaembedediting.js @@ -184,12 +184,12 @@ export default class MediaEmbedEditing extends Plugin { } ); // Model -> Data - conversion.for( 'dataDowncast' ).elementToElement( { + conversion.for( 'dataDowncast' ).elementToStructure( { model: 'media', - view: ( modelElement, { writer } ) => { + view: ( modelElement, conversionApi ) => { const url = modelElement.getAttribute( 'url' ); - return createMediaFigureElement( writer, registry, url, { + return createMediaFigureElement( conversionApi, registry, url, { elementName, renderMediaPreview: url && renderMediaPreview } ); @@ -204,16 +204,16 @@ export default class MediaEmbedEditing extends Plugin { } ) ); // Model -> View (element) - conversion.for( 'editingDowncast' ).elementToElement( { + conversion.for( 'editingDowncast' ).elementToStructure( { model: 'media', - view: ( modelElement, { writer } ) => { + view: ( modelElement, conversionApi ) => { const url = modelElement.getAttribute( 'url' ); - const figure = createMediaFigureElement( writer, registry, url, { + const figure = createMediaFigureElement( conversionApi, registry, url, { elementName, renderForEditingView: true } ); - return toMediaWidget( figure, writer, t( 'media widget' ) ); + return toMediaWidget( figure, conversionApi.writer, t( 'media widget' ) ); } } ); diff --git a/packages/ckeditor5-media-embed/src/utils.js b/packages/ckeditor5-media-embed/src/utils.js index 0666ee212a8..b5b00eb47ba 100644 --- a/packages/ckeditor5-media-embed/src/utils.js +++ b/packages/ckeditor5-media-embed/src/utils.js @@ -73,10 +73,11 @@ export function isMediaWidget( viewElement ) { * @param {Boolean} [options.renderForEditingView] * @returns {module:engine/view/containerelement~ContainerElement} */ -export function createMediaFigureElement( writer, registry, url, options ) { +export function createMediaFigureElement( { writer, slotFor }, registry, url, options ) { const figure = writer.createContainerElement( 'figure', { class: 'media' } ); writer.insert( writer.createPositionAt( figure, 0 ), registry.getMediaViewElement( writer, url, options ) ); + writer.insert( writer.createPositionAt( figure, 1 ), slotFor( 'children' ) ); return figure; } diff --git a/packages/ckeditor5-media-embed/tests/automediaembed.js b/packages/ckeditor5-media-embed/tests/automediaembed.js index 5aa36c7d420..9586b19bb75 100644 --- a/packages/ckeditor5-media-embed/tests/automediaembed.js +++ b/packages/ckeditor5-media-embed/tests/automediaembed.js @@ -75,9 +75,9 @@ describe( 'AutoMediaEmbed - integration', () => { clock.tick( 100 ); - expect( getData( editor.model ) ).to.equal( - '[]' - ); + // expect( getData( editor.model ) ).to.equal( + // '[]' + // ); } ); it( 'can undo auto-embeding', () => { diff --git a/packages/ckeditor5-page-break/src/pagebreakediting.js b/packages/ckeditor5-page-break/src/pagebreakediting.js index 3b8d4c8497e..4d60e3cc5db 100644 --- a/packages/ckeditor5-page-break/src/pagebreakediting.js +++ b/packages/ckeditor5-page-break/src/pagebreakediting.js @@ -41,7 +41,7 @@ export default class PageBreakEditing extends Plugin { allowWhere: '$block' } ); - conversion.for( 'dataDowncast' ).elementToElement( { + conversion.for( 'dataDowncast' ).elementToStructure( { model: 'pageBreak', view: ( modelElement, { writer } ) => { const divElement = writer.createContainerElement( 'div', { @@ -62,7 +62,7 @@ export default class PageBreakEditing extends Plugin { } } ); - conversion.for( 'editingDowncast' ).elementToElement( { + conversion.for( 'editingDowncast' ).elementToStructure( { model: 'pageBreak', view: ( modelElement, { writer } ) => { const label = t( 'Page break' ); diff --git a/packages/ckeditor5-widget/tests/widget-events.js b/packages/ckeditor5-widget/tests/widget-events.js index a34f6d712ee..fd85f0dbcdc 100644 --- a/packages/ckeditor5-widget/tests/widget-events.js +++ b/packages/ckeditor5-widget/tests/widget-events.js @@ -79,7 +79,7 @@ describe( 'Widget - Events', () => { function defineConverters( editor ) { editor.conversion.for( 'editingDowncast' ) - .elementToElement( { + .elementToStructure( { model: 'simpleWidgetElement', view: ( modelElement, { writer } ) => { const widgetElement = createWidgetView( modelElement, { writer } ); @@ -89,7 +89,7 @@ describe( 'Widget - Events', () => { } ); editor.conversion.for( 'dataDowncast' ) - .elementToElement( { + .elementToStructure( { model: 'simpleWidgetElement', view: createWidgetView } ); diff --git a/packages/ckeditor5-widget/tests/widget.js b/packages/ckeditor5-widget/tests/widget.js index df8fe06a9f7..f3d33a6b573 100644 --- a/packages/ckeditor5-widget/tests/widget.js +++ b/packages/ckeditor5-widget/tests/widget.js @@ -96,12 +96,13 @@ describe( 'Widget', () => { .elementToElement( { model: 'imageBlock', view: 'img' } ) .elementToElement( { model: 'blockQuote', view: 'blockquote' } ) .elementToElement( { model: 'div', view: 'div' } ) - .elementToElement( { + .elementToStructure( { model: 'widget', - view: ( modelItem, { writer } ) => { + view: ( modelItem, { writer, slotFor } ) => { const b = writer.createAttributeElement( 'b' ); const div = writer.createContainerElement( 'div' ); writer.insert( writer.createPositionAt( div, 0 ), b ); + writer.insert( writer.createPositionAt( div, 0 ), slotFor( 'children' ) ); return toWidget( div, writer, { label: 'element label' } ); } diff --git a/packages/ckeditor5-widget/tests/widgetresize.js b/packages/ckeditor5-widget/tests/widgetresize.js index 68cee377b50..0317e0ef9a0 100644 --- a/packages/ckeditor5-widget/tests/widgetresize.js +++ b/packages/ckeditor5-widget/tests/widgetresize.js @@ -697,7 +697,7 @@ describe( 'WidgetResize', () => { } ); editor.conversion.for( 'downcast' ) - .elementToElement( { + .elementToStructure( { model: 'widget', view: ( modelItem, { writer } ) => { const parentDiv = writer.createContainerElement( 'div' ); diff --git a/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js b/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js index b7d54ab4b80..b9718157b97 100644 --- a/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js +++ b/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js @@ -1872,13 +1872,14 @@ describe( 'WidgetTypeAround', () => { } ); editor.conversion.for( 'downcast' ) - .elementToElement( { + .elementToStructure( { model: 'blockWidget', - view: ( modelItem, { writer } ) => { + view: ( modelItem, { writer, slotFor } ) => { const container = writer.createContainerElement( 'div' ); const viewText = writer.createText( 'block-widget' ); writer.insert( writer.createPositionAt( container, 0 ), viewText ); + writer.insert( writer.createPositionAt( container, 0 ), slotFor( 'children' ) ); return toWidget( container, writer, { label: 'block widget' @@ -1899,13 +1900,14 @@ describe( 'WidgetTypeAround', () => { } ); editor.conversion.for( 'downcast' ) - .elementToElement( { + .elementToStructure( { model: 'inlineWidget', - view: ( modelItem, { writer } ) => { + view: ( modelItem, { writer, slotFor } ) => { const container = writer.createContainerElement( 'inlineWidget' ); const viewText = writer.createText( 'inline-widget' ); writer.insert( writer.createPositionAt( container, 0 ), viewText ); + writer.insert( writer.createPositionAt( container, 0 ), slotFor( 'children' ) ); return toWidget( container, writer, { label: 'inline widget' From fd9ecfe3e88a706e778ade98e2d6b9db9d694f10 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 08:51:57 +0200 Subject: [PATCH 077/140] Cleanup classic editor manual test. --- .../tests/manual/classiceditor.js | 39 +------------------ .../src/conversion/downcasthelpers.js | 5 +-- .../src/image/imageblockediting.js | 4 +- .../src/image/imageinlineediting.js | 2 +- packages/ckeditor5-image/src/image/utils.js | 7 +--- .../src/imagecaption/imagecaptionediting.js | 28 ------------- .../ckeditor5-image/tests/image/converters.js | 2 +- packages/ckeditor5-image/tests/image/utils.js | 6 ++- 8 files changed, 13 insertions(+), 80 deletions(-) diff --git a/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js b/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js index d5eccd9d366..74ec25ac495 100644 --- a/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js +++ b/packages/ckeditor5-editor-classic/tests/manual/classiceditor.js @@ -14,50 +14,13 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'; import { createObserver } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; -import { Command, Plugin } from '@ckeditor/ckeditor5-core'; let editor, editable, observer; -class MyCommand extends Command { - execute() { - this.editor.model.change( writer => { - // Insert * at the current selection position - // in a way that will result in creating a valid model structure. - this.editor.model.insertContent( writer.createElement( 'el' ) ); - } ); - } - - refresh() { - this.isEnabled = true; - } -} - -class TestPlugin extends Plugin { - init() { - this.editor.commands.add( 'myCommand', new MyCommand( this.editor ) ); - - this.editor.model.schema.register( 'el', { - inheritAllFrom: '$block' - } ); - - this.editor.conversion.for( 'downcast' ).elementToElement( { - model: 'el', - view: ( modelElement, { writer } ) => { - const section = writer.createContainerElement( 'section' ); - const img = writer.createEmptyElement( 'img' ); - - writer.insert( writer.createPositionAt( section, 0 ), img ); - - return section; - } - } ); - } -} - function initEditor() { ClassicEditor .create( document.querySelector( '#editor' ), { - plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic, TestPlugin ], + plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic ], toolbar: [ 'heading', '|', 'bold', 'italic', 'undo', 'redo' ] } ) .then( newEditor => { diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 3d42f8c384f..1fa08d360b4 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1703,6 +1703,7 @@ function downcastElementToStructure( config ) { config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); + config.model.children = true; return dispatcher => { dispatcher.on( @@ -1711,9 +1712,7 @@ function downcastElementToStructure( config ) { { priority: config.converterPriority || 'normal' } ); - if ( config.model.children || config.model.attributes.length ) { - dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); - } + dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } ); }; } diff --git a/packages/ckeditor5-image/src/image/imageblockediting.js b/packages/ckeditor5-image/src/image/imageblockediting.js index 15a8caa0ec5..6d42ad4eac3 100644 --- a/packages/ckeditor5-image/src/image/imageblockediting.js +++ b/packages/ckeditor5-image/src/image/imageblockediting.js @@ -92,14 +92,14 @@ export default class ImageBlockEditing extends Plugin { conversion.for( 'dataDowncast' ) .elementToStructure( { model: 'imageBlock', - view: ( modelElement, { writer, slotFor } ) => createBlockImageViewElement( writer, slotFor, 'imageBlock' ) + view: ( modelElement, { writer, slotFor } ) => createBlockImageViewElement( writer, slotFor( 'children' ) ) } ); conversion.for( 'editingDowncast' ) .elementToStructure( { model: 'imageBlock', view: ( modelElement, { writer, slotFor } ) => imageUtils.toImageWidget( - createBlockImageViewElement( writer, slotFor, 'imageBlock' ), writer, t( 'image widget' ) + createBlockImageViewElement( writer, slotFor( 'children' ) ), writer, t( 'image widget' ) ) } ); diff --git a/packages/ckeditor5-image/src/image/imageinlineediting.js b/packages/ckeditor5-image/src/image/imageinlineediting.js index b08c846de9f..7875467e980 100644 --- a/packages/ckeditor5-image/src/image/imageinlineediting.js +++ b/packages/ckeditor5-image/src/image/imageinlineediting.js @@ -107,7 +107,7 @@ export default class ImageInlineEditing extends Plugin { .elementToStructure( { model: 'imageInline', view: ( modelElement, { writer } ) => imageUtils.toImageWidget( - createInlineImageViewElement( writer, 'imageInline' ), writer, t( 'image widget' ) + createInlineImageViewElement( writer ), writer, t( 'image widget' ) ) } ); diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 9ca1f3de739..3694d55027c 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -36,15 +36,12 @@ export function createInlineImageViewElement( writer ) { return container; } -export function createBlockImageViewElement( writer, slotFor = false ) { +export function createBlockImageViewElement( writer, slotElement ) { const emptyElement = writer.createEmptyElement( 'img' ); const container = writer.createContainerElement( 'figure', { class: 'image' } ); writer.insert( writer.createPositionAt( container, 0 ), emptyElement ); - - if ( slotFor ) { - writer.insert( writer.createPositionAt( container, 1 ), slotFor( 'children' ) ); - } + writer.insert( writer.createPositionAt( container, 1 ), slotElement ); return container; } diff --git a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js index c03025c8010..67eca3ec0ae 100644 --- a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js +++ b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js @@ -134,9 +134,6 @@ export default class ImageCaptionEditing extends Plugin { return toWidgetEditable( figcaptionElement, writer ); } } ); - - editor.editing.mapper.on( 'modelToViewPosition', mapModelPositionToView( view ) ); - editor.data.mapper.on( 'modelToViewPosition', mapModelPositionToView( view ) ); } /** @@ -244,28 +241,3 @@ export default class ImageCaptionEditing extends Plugin { this._savedCaptionsMap.set( imageModelElement, caption.toJSON() ); } } - -// Creates a mapper callback that reverses the order of `` and `
` in the image. -// Without it, `
` would precede the `` in the conversion. -// -// ^ ->
^
-// -// @private -// @param {module:engine/view/view~View} editingView -// @returns {Function} -function mapModelPositionToView( editingView ) { - return ( evt, data ) => { - const modelPosition = data.modelPosition; - const parent = modelPosition.parent; - - if ( !parent.is( 'element', 'imageBlock' ) ) { - return; - } - - const viewElement = data.mapper.toViewElement( parent ); - - // The "img" element is inserted by ImageBlockEditing during the downcast conversion via - // an explicit view position so the "0" position does not need any mapping. - data.viewPosition = editingView.createPositionAt( viewElement, modelPosition.offset + 1 ); - }; -} diff --git a/packages/ckeditor5-image/tests/image/converters.js b/packages/ckeditor5-image/tests/image/converters.js index 856e590e872..9675dd83d22 100644 --- a/packages/ckeditor5-image/tests/image/converters.js +++ b/packages/ckeditor5-image/tests/image/converters.js @@ -50,7 +50,7 @@ describe( 'Image converters', () => { } ); const imageEditingElementCreator = ( modelElement, { writer, slotFor } ) => - imageUtils.toImageWidget( createBlockImageViewElement( writer, slotFor ), writer, '' ); + imageUtils.toImageWidget( createBlockImageViewElement( writer, slotFor( 'children' ) ), writer, '' ); const imageInlineEditingElementCreator = ( modelElement, { writer } ) => imageUtils.toImageWidget( createInlineImageViewElement( writer ), writer, '' ); diff --git a/packages/ckeditor5-image/tests/image/utils.js b/packages/ckeditor5-image/tests/image/utils.js index c34223a4338..02e4dfebd7b 100644 --- a/packages/ckeditor5-image/tests/image/utils.js +++ b/packages/ckeditor5-image/tests/image/utils.js @@ -290,12 +290,14 @@ describe( 'image utils', () => { } ); it( 'should create a figure element for "image" type', () => { - const element = createBlockImageViewElement( writer ); + const slotElement = writer.createEmptyElement( '$slot' ); + const element = createBlockImageViewElement( writer, slotElement ); expect( element.is( 'element', 'figure' ) ).to.be.true; expect( element.hasClass( 'image' ) ).to.be.true; - expect( element.childCount ).to.equal( 1 ); + expect( element.childCount ).to.equal( 2 ); expect( element.getChild( 0 ).is( 'emptyElement', 'img' ) ).to.be.true; + expect( element.getChild( 1 ).is( 'emptyElement', '$slot' ) ).to.be.true; } ); } ); From 375bf36a5156586717dd3a0c6d6e39bee257e0de Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 08:52:40 +0200 Subject: [PATCH 078/140] Cleanup element reconversion manual test. --- .../ckeditor5-engine/tests/manual/element-reconversion.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/ckeditor5-engine/tests/manual/element-reconversion.js b/packages/ckeditor5-engine/tests/manual/element-reconversion.js index 525288790e4..ccdffb131d4 100644 --- a/packages/ckeditor5-engine/tests/manual/element-reconversion.js +++ b/packages/ckeditor5-engine/tests/manual/element-reconversion.js @@ -47,11 +47,6 @@ function Items( editor ) { const attributes = { class: 'items ' }; if ( mode === 'threshold' ) { - writer.createEmptyElement( 'div', { - 'data-amount': getThreshold( modelElement.childCount ), - ...attributes - } ); - return writer.createContainerElement( 'div', { 'data-amount': getThreshold( modelElement.childCount ), ...attributes From ca040ca21528089f28baa5d83d7e55f05d43098e Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 08:59:18 +0200 Subject: [PATCH 079/140] Bring back assertion in media-embed test. --- packages/ckeditor5-media-embed/tests/automediaembed.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-media-embed/tests/automediaembed.js b/packages/ckeditor5-media-embed/tests/automediaembed.js index 9586b19bb75..5aa36c7d420 100644 --- a/packages/ckeditor5-media-embed/tests/automediaembed.js +++ b/packages/ckeditor5-media-embed/tests/automediaembed.js @@ -75,9 +75,9 @@ describe( 'AutoMediaEmbed - integration', () => { clock.tick( 100 ); - // expect( getData( editor.model ) ).to.equal( - // '[]' - // ); + expect( getData( editor.model ) ).to.equal( + '[]' + ); } ); it( 'can undo auto-embeding', () => { From 9814ff089c5c0a7548022fa5b8483e3ec8457c5b Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 09:21:32 +0200 Subject: [PATCH 080/140] Add proper error code. --- .../src/conversion/downcasthelpers.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 1fa08d360b4..460716bad3f 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1050,18 +1050,19 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { if ( hasContent ) { /** - * The Easy Image feature requires one of the following plugins to be loaded to work correctly: + * Only one container element without any children elements other than + * {@link module:engine/view/uielement~UIElement `UIElement`}s should be created in + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} function. * - * * {@link module:image/imageblock~ImageBlock}, - * * {@link module:image/imageinline~ImageInline}, - * * {@link module:image/image~Image} (loads both `ImageBlock` and `ImageInline`) + * Please make sure you don't create more than one element in + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} and if you need + * to create multiple elements use {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure} + * instead. * - * Please make sure your editor configuration is correct. - * - * @error easy-image-image-feature-missing - * @param {module:core/editor/editor~Editor} editor + * @error conversion-element-to-element-created-multiple-elements + * @param {module:engine/model/element~Element} viewElement */ - logWarning( 'element-to-element-used-with-multiple-elements', viewElement ); + logWarning( 'conversion-element-to-element-created-multiple-elements', viewElement ); } // Consume an element insertion and all present attributes that are specified as a reconversion triggers. From 72206a73d2e7c3436e7e02d7be8bc92c71e22601 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 09:48:27 +0200 Subject: [PATCH 081/140] Extract children validation warning to separate function. --- .../src/conversion/downcasthelpers.js | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 460716bad3f..4429848a552 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1045,25 +1045,8 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { return; } - const hasContent = Array.from( viewElement.getChildren() ) - .some( element => !element.is( 'uiElement' ) ); - - if ( hasContent ) { - /** - * Only one container element without any children elements other than - * {@link module:engine/view/uielement~UIElement `UIElement`}s should be created in - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} function. - * - * Please make sure you don't create more than one element in - * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} and if you need - * to create multiple elements use {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure} - * instead. - * - * @error conversion-element-to-element-created-multiple-elements - * @param {module:engine/model/element~Element} viewElement - */ - logWarning( 'conversion-element-to-element-created-multiple-elements', viewElement ); - } + // Check if only one element has been created. + validateChildren( viewElement.getChildren() ); // Consume an element insertion and all present attributes that are specified as a reconversion triggers. consumer( data.item, conversionApi.consumable ); @@ -2124,6 +2107,31 @@ function createConsumer( model ) { }; } +// Check if given element list contains only UI elements and warns otherwise. +// +// @param {module:engine/view/element~Element} viewElement. +function validateChildren( viewElement ) { + const children = Array.from( viewElement.getChildren() ); + const hasNonUIchildren = children.some( element => !element.is( 'uiElement' ) ); + + if ( hasNonUIchildren ) { + /** + * Only one container element without any children elements other than + * {@link module:engine/view/uielement~UIElement `UIElement`}s should be created in + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} function. + * + * Please make sure you don't create more than one element in + * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement} and if you need + * to create multiple elements use {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure} + * instead. + * + * @error conversion-element-to-element-created-multiple-elements + * @param {module:engine/model/element~Element} viewElement + */ + logWarning( 'conversion-element-to-element-created-multiple-elements', viewElement ); + } +} + // Creates a function that create view slots. // // @param {module:engine/model/element~Element} element From c919257e5d3621068372a8567b8091c37d6f0549 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 12:31:35 +0200 Subject: [PATCH 082/140] Improve docs for new image helpers. --- packages/ckeditor5-image/src/image/utils.js | 23 ++++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 3694d55027c..97bc85b6e09 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -10,13 +10,7 @@ import { first } from 'ckeditor5/src/utils'; /** - * Creates a view element representing the image of provided image type. - * - * An 'imageBlock' type (block image): - * - *
- * - * An 'imageInline' type (inline image): + * Creates a view element representing the inline image. * * * @@ -24,7 +18,6 @@ import { first } from 'ckeditor5/src/utils'; * * @protected * @param {module:engine/view/downcastwriter~DowncastWriter} writer - * @param {'imageBlock'|'imageInline'} imageType The type of created image. * @returns {module:engine/view/containerelement~ContainerElement} */ export function createInlineImageViewElement( writer ) { @@ -36,6 +29,20 @@ export function createInlineImageViewElement( writer ) { return container; } +/** + * Creates a view element representing the block image. + * + * An 'imageBlock' type (block image): + * + *
+ * + * Note that `alt` and `src` attributes are converted separately, so they are not included. + * + * @protected + * @param {module:engine/view/downcastwriter~DowncastWriter} writer + * @param {module:engine/view/element~Element} slotElement + * @returns {module:engine/view/containerelement~ContainerElement} + */ export function createBlockImageViewElement( writer, slotElement ) { const emptyElement = writer.createEmptyElement( 'img' ); const container = writer.createContainerElement( 'figure', { class: 'image' } ); From cba48c3e2aa84536b898a02513f675c7ff2c86d7 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 12:33:55 +0200 Subject: [PATCH 083/140] Minor fix. --- packages/ckeditor5-engine/src/conversion/downcasthelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index 4429848a552..c1cc46c62bc 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1046,7 +1046,7 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) { } // Check if only one element has been created. - validateChildren( viewElement.getChildren() ); + validateChildren( viewElement ); // Consume an element insertion and all present attributes that are specified as a reconversion triggers. consumer( data.item, conversionApi.consumable ); @@ -2107,7 +2107,7 @@ function createConsumer( model ) { }; } -// Check if given element list contains only UI elements and warns otherwise. +// Check if given element children list contains only UI elements and warns otherwise. // // @param {module:engine/view/element~Element} viewElement. function validateChildren( viewElement ) { From 9477a109faf5964c5ba1a73ef8ec86b4d8839896 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 12:37:25 +0200 Subject: [PATCH 084/140] Update comment. --- packages/ckeditor5-image/src/image/utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 97bc85b6e09..9ab3f2f5150 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -32,8 +32,6 @@ export function createInlineImageViewElement( writer ) { /** * Creates a view element representing the block image. * - * An 'imageBlock' type (block image): - * *
* * Note that `alt` and `src` attributes are converted separately, so they are not included. From de7f0d5555de1f1a8cf6783b3d5a009ddfd8897f Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 12:46:34 +0200 Subject: [PATCH 085/140] Update jsdoc. --- packages/ckeditor5-media-embed/src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-media-embed/src/utils.js b/packages/ckeditor5-media-embed/src/utils.js index b5b00eb47ba..b166123832d 100644 --- a/packages/ckeditor5-media-embed/src/utils.js +++ b/packages/ckeditor5-media-embed/src/utils.js @@ -64,7 +64,7 @@ export function isMediaWidget( viewElement ) { *
[ non-semantic media preview for "foo" ]
* * - * @param {module:engine/view/downcastwriter~DowncastWriter} writer + * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi * @param {module:media-embed/mediaregistry~MediaRegistry} registry * @param {String} url * @param {Object} options From bbf4b49e1022b1d67affbb61cb446fe3e3d18462 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 13:12:01 +0200 Subject: [PATCH 086/140] Add tests for the warning. --- .../tests/conversion/downcasthelpers.js | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index b51dee7583c..4411d56a718 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -647,6 +647,60 @@ describe( 'DowncastHelpers', () => { expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); } ); } ); + + describe( 'with multiple child elements', () => { + it( 'warns if multiple child elements created', () => { + let viewElement; + + testUtils.sinon.stub( console, 'warn' ); + + downcastHelpers.elementToElement( { + model: 'multiItemBox', + view: ( modelElement, { writer } ) => { + viewElement = writer.createContainerElement( 'div' ); + + writer.insert( writer.createPositionAt( viewElement, 0 ), writer.createEmptyElement( 'p' ) ); + + return viewElement; + } + } ); + + model.change( writer => { + writer.insertElement( 'multiItemBox', null, modelRoot, 0 ); + } ); + + sinon.assert.calledOnce( console.warn ); + sinon.assert.calledWithExactly( console.warn, + sinon.match( /^conversion-element-to-element-created-multiple-elements/ ), + viewElement, + sinon.match.string // Link to the documentation + ); + } ); + + it( 'does not warn if multiple child UI elements created', () => { + let viewElement; + + testUtils.sinon.stub( console, 'warn' ); + + downcastHelpers.elementToElement( { + model: 'multiItemBox', + view: ( modelElement, { writer } ) => { + viewElement = writer.createContainerElement( 'div' ); + + writer.insert( writer.createPositionAt( viewElement, 0 ), writer.createUIElement( 'div' ) ); + writer.insert( writer.createPositionAt( viewElement, 1 ), writer.createUIElement( 'span' ) ); + + return viewElement; + } + } ); + + model.change( writer => { + writer.insertElement( 'multiItemBox', null, modelRoot, 0 ); + } ); + + sinon.assert.notCalled( console.warn ); + } ); + } ); } ); describe( 'elementToStructure()', () => { @@ -947,36 +1001,6 @@ describe( 'DowncastHelpers', () => { expect( viewAfter ).to.equal( viewBefore ); } ); - - it( 'should not reconvert on child element added', () => { - model.schema.register( 'paragraph', { - inheritAllFrom: '$block', - allowIn: 'simpleBlock' - } ); - - downcastHelpers.elementToElement( { - model: 'paragraph', - view: 'p' - } ); - - setModelData( model, '' ); - - controller.downcastDispatcher.on( 'insert', ( evt, data ) => { - expect( data ).to.not.have.property( 'reconversion' ); - } ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '

' ); - - expect( viewAfter ).to.equal( viewBefore ); - } ); } ); describe( 'with simple block view structure (with slots, without reconvert on children list change)', () => { @@ -1122,26 +1146,6 @@ describe( 'DowncastHelpers', () => { expect( paraAfter, 'para' ).to.equal( paraBefore ); expect( textAfter, 'text' ).to.equal( textBefore ); } ); - - it( 'should not reconvert on child element added', () => { - setModelData( model, '' ); - - controller.downcastDispatcher.on( 'insert', ( evt, data ) => { - expect( data ).to.not.have.property( 'reconversion' ); - } ); - - const [ viewBefore ] = getNodes(); - - model.change( writer => { - writer.insertElement( 'paragraph', modelRoot.getChild( 0 ), 0 ); - } ); - - const [ viewAfter ] = getNodes(); - - expectResult( '

' ); - - expect( viewAfter ).to.equal( viewBefore ); - } ); } ); describe( 'with simple block view structure (reconvert on child add)', () => { From 08cde2c814ef97646fbe00751b5f89af20a1f714 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 13 Oct 2021 13:27:30 +0200 Subject: [PATCH 087/140] Test description update. --- packages/ckeditor5-engine/tests/conversion/downcasthelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index 4411d56a718..7567d8bd15c 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -649,7 +649,7 @@ describe( 'DowncastHelpers', () => { } ); describe( 'with multiple child elements', () => { - it( 'warns if multiple child elements created', () => { + it( 'warns if multiple child elements are created', () => { let viewElement; testUtils.sinon.stub( console, 'warn' ); @@ -677,7 +677,7 @@ describe( 'DowncastHelpers', () => { ); } ); - it( 'does not warn if multiple child UI elements created', () => { + it( 'does not warn if multiple child UI elements are created', () => { let viewElement; testUtils.sinon.stub( console, 'warn' ); From 90368236c41a3bf2bf81868a17d7d3cb05befc9f Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 15 Oct 2021 16:10:44 +0200 Subject: [PATCH 088/140] Code cleanup. --- .../ckeditor5-engine/src/conversion/downcasthelpers.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js index e83d4d46396..518ec16fa18 100644 --- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js @@ -1696,7 +1696,6 @@ function downcastElementToStructure( config ) { config.model = normalizeModelElementConfig( config.model ); config.view = normalizeToElementConfig( config.view, 'container' ); - config.model.children = true; // Trigger reconversion on children list change because it always needs to use slots to put children in proper places. // This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element. @@ -2126,9 +2125,9 @@ function createConsumer( model ) { // @param {module:engine/view/element~Element} viewElement. function validateChildren( viewElement ) { const children = Array.from( viewElement.getChildren() ); - const hasNonUIchildren = children.some( element => !element.is( 'uiElement' ) ); + const hasNonUiChildren = children.some( element => !element.is( 'uiElement' ) ); - if ( hasNonUIchildren ) { + if ( hasNonUiChildren ) { /** * Only one container element without any children elements other than * {@link module:engine/view/uielement~UIElement `UIElement`}s should be created in @@ -2142,7 +2141,7 @@ function validateChildren( viewElement ) { * @error conversion-element-to-element-created-multiple-elements * @param {module:engine/model/element~Element} viewElement */ - logWarning( 'conversion-element-to-element-created-multiple-elements', viewElement ); + logWarning( 'conversion-element-to-element-created-multiple-elements', { viewElement } ); } } From 4949293d807ad3baa4a02422a851c41289d5f839 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 15 Oct 2021 16:17:38 +0200 Subject: [PATCH 089/140] Updated test. --- packages/ckeditor5-engine/tests/conversion/downcasthelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index b58446bc024..ac939fce8c3 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -831,7 +831,7 @@ describe( 'DowncastHelpers', () => { sinon.assert.calledOnce( console.warn ); sinon.assert.calledWithExactly( console.warn, sinon.match( /^conversion-element-to-element-created-multiple-elements/ ), - viewElement, + { viewElement }, sinon.match.string // Link to the documentation ); } ); From 15357a547a2078fca4857651ff7a22fe49775a6a Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Fri, 15 Oct 2021 18:04:38 +0200 Subject: [PATCH 090/140] PoC. --- .../ckeditor5-code-block/src/converters.js | 4 +-- .../src/view/downcastwriter.js | 11 ++++-- .../src/horizontallineediting.js | 8 ++--- .../src/htmlembedediting.js | 13 +++---- .../ckeditor5-html-support/src/converters.js | 18 +++++----- .../ckeditor5-image/src/image/converters.js | 12 +++---- packages/ckeditor5-image/src/image/utils.js | 20 ++++------- packages/ckeditor5-media-embed/src/utils.js | 10 +++--- .../src/pagebreakediting.js | 16 ++++----- .../src/converters/downcast.js | 35 ++++++++----------- 10 files changed, 66 insertions(+), 81 deletions(-) diff --git a/packages/ckeditor5-code-block/src/converters.js b/packages/ckeditor5-code-block/src/converters.js index 227847a0365..4d63730d0f9 100644 --- a/packages/ckeditor5-code-block/src/converters.js +++ b/packages/ckeditor5-code-block/src/converters.js @@ -69,12 +69,12 @@ export function modelToViewCodeBlockInsertion( model, languageDefs, useLabels = preAttributes.spellcheck = 'false'; } - const pre = writer.createContainerElement( 'pre', preAttributes ); const code = writer.createContainerElement( 'code', { class: languagesToClasses[ codeBlockLanguage ] || null } ); - writer.insert( writer.createPositionAt( pre, 0 ), code ); + const pre = writer.createContainerElement( 'pre', preAttributes, code ); + writer.insert( targetViewPosition, pre ); mapper.bindElements( data.item, code ); }; diff --git a/packages/ckeditor5-engine/src/view/downcastwriter.js b/packages/ckeditor5-engine/src/view/downcastwriter.js index 6d03339713d..19fe71a9233 100644 --- a/packages/ckeditor5-engine/src/view/downcastwriter.js +++ b/packages/ckeditor5-engine/src/view/downcastwriter.js @@ -222,10 +222,17 @@ export default class DowncastWriter { * @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}. + * @param {module:engine/view/node~Node|Iterable.} [children] + * A list of nodes to be inserted into created element. * @returns {module:engine/view/containerelement~ContainerElement} Created element. */ - createContainerElement( name, attributes, options = {} ) { - const containerElement = new ContainerElement( this.document, name, attributes ); + createContainerElement( name, attributes, options = {}, children = null ) { + if ( !isPlainObject( options ) ) { + children = options; + options = {}; + } + + const containerElement = new ContainerElement( this.document, name, attributes, children ); if ( options.isAllowedInsideAttributeElement !== undefined ) { containerElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement; diff --git a/packages/ckeditor5-horizontal-line/src/horizontallineediting.js b/packages/ckeditor5-horizontal-line/src/horizontallineediting.js index 7cf7524fb81..2e0f9458cf8 100644 --- a/packages/ckeditor5-horizontal-line/src/horizontallineediting.js +++ b/packages/ckeditor5-horizontal-line/src/horizontallineediting.js @@ -52,14 +52,14 @@ export default class HorizontalLineEditing extends Plugin { model: 'horizontalLine', view: ( modelElement, { writer } ) => { const label = t( 'Horizontal line' ); - const viewWrapper = writer.createContainerElement( 'div' ); - const viewHrElement = writer.createEmptyElement( 'hr' ); + + const viewWrapper = writer.createContainerElement( 'div', null, + writer.createEmptyElement( 'hr' ) + ); writer.addClass( 'ck-horizontal-line', viewWrapper ); writer.setCustomProperty( 'hr', true, viewWrapper ); - writer.insert( writer.createPositionAt( viewWrapper, 0 ), viewHrElement ); - return toHorizontalLineWidget( viewWrapper, writer, label ); } } ); diff --git a/packages/ckeditor5-html-embed/src/htmlembedediting.js b/packages/ckeditor5-html-embed/src/htmlembedediting.js index 675a15c91a4..aaed4bf0ec8 100644 --- a/packages/ckeditor5-html-embed/src/htmlembedediting.js +++ b/packages/ckeditor5-html-embed/src/htmlembedediting.js @@ -123,13 +123,6 @@ export default class HtmlEmbedEditing extends Plugin { view: ( modelElement, { writer } ) => { let domContentWrapper, state, props; - const viewContainer = writer.createContainerElement( 'div', { - class: 'raw-html-embed', - 'data-html-embed-label': t( 'HTML snippet' ), - dir: editor.locale.uiLanguageDirection - } ); - // Widget cannot be a raw element because the widget system would not be able - // to add its UI to it. Thus, we need this wrapper. const viewContentWrapper = writer.createRawElement( 'div', { class: 'raw-html-embed__content-wrapper' }, function( domElement ) { @@ -214,7 +207,11 @@ export default class HtmlEmbedEditing extends Plugin { } }; - writer.insert( writer.createPositionAt( viewContainer, 0 ), viewContentWrapper ); + const viewContainer = writer.createContainerElement( 'div', { + class: 'raw-html-embed', + 'data-html-embed-label': t( 'HTML snippet' ), + dir: editor.locale.uiLanguageDirection + }, viewContentWrapper ); writer.setCustomProperty( 'rawHtmlApi', rawHtmlApi, viewContainer ); writer.setCustomProperty( 'rawHtml', true, viewContainer ); diff --git a/packages/ckeditor5-html-support/src/converters.js b/packages/ckeditor5-html-support/src/converters.js index 95d29b6761b..ee8a9296e81 100644 --- a/packages/ckeditor5-html-support/src/converters.js +++ b/packages/ckeditor5-html-support/src/converters.js @@ -40,15 +40,6 @@ export function toObjectWidgetConverter( editor, { view: viewName, isInline } ) return ( modelElement, { writer, consumable } ) => { const widgetLabel = t( 'HTML object' ); - // Widget cannot be a raw element because the widget system would not be able - // to add its UI to it. Thus, we need separate view container. - const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div', { - class: 'html-object-embed', - 'data-html-object-embed-label': widgetLabel - }, { - isAllowedInsideAttributeElement: isInline - } ); - const viewElement = createObjectView( viewName, modelElement, writer ); writer.addClass( 'html-object-embed__content', viewElement ); @@ -57,7 +48,14 @@ export function toObjectWidgetConverter( editor, { view: viewName, isInline } ) setViewAttributes( writer, viewAttributes, viewElement ); } - writer.insert( writer.createPositionAt( viewContainer, 0 ), viewElement ); + // Widget cannot be a raw element because the widget system would not be able + // to add its UI to it. Thus, we need separate view container. + const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div', { + class: 'html-object-embed', + 'data-html-object-embed-label': widgetLabel + }, { + isAllowedInsideAttributeElement: isInline + }, viewElement ); return toWidget( viewContainer, writer, { widgetLabel } ); }; diff --git a/packages/ckeditor5-image/src/image/converters.js b/packages/ckeditor5-image/src/image/converters.js index 05ff1bef3c1..a1311d61e53 100644 --- a/packages/ckeditor5-image/src/image/converters.js +++ b/packages/ckeditor5-image/src/image/converters.js @@ -232,13 +232,11 @@ export function downcastSourcesAttribute( imageUtils ) { if ( data.attributeNewValue && data.attributeNewValue.length ) { // Make sure does not break attribute elements, for instance in linked images. - const pictureElement = viewWriter.createContainerElement( 'picture', {}, { isAllowedInsideAttributeElement: true } ); - - for ( const sourceAttributes of data.attributeNewValue ) { - const sourceElement = viewWriter.createEmptyElement( 'source', sourceAttributes ); - - viewWriter.insert( viewWriter.createPositionAt( pictureElement, 'end' ), sourceElement ); - } + const pictureElement = viewWriter.createContainerElement( 'picture', {}, { isAllowedInsideAttributeElement: true }, + data.attributeNewValue.map( sourceAttributes => { + return viewWriter.createEmptyElement( 'source', sourceAttributes ); + } ) + ); // Collect all wrapping attribute elements. const attributeElements = []; diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 9ab3f2f5150..96b225583d0 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -21,12 +21,9 @@ import { first } from 'ckeditor5/src/utils'; * @returns {module:engine/view/containerelement~ContainerElement} */ export function createInlineImageViewElement( writer ) { - const emptyElement = writer.createEmptyElement( 'img' ); - const container = writer.createContainerElement( 'span', { class: 'image-inline' }, { isAllowedInsideAttributeElement: true } ); - - writer.insert( writer.createPositionAt( container, 0 ), emptyElement ); - - return container; + return writer.createContainerElement( 'span', { class: 'image-inline' }, { isAllowedInsideAttributeElement: true }, [ + writer.createEmptyElement( 'img' ) + ] ); } /** @@ -42,13 +39,10 @@ export function createInlineImageViewElement( writer ) { * @returns {module:engine/view/containerelement~ContainerElement} */ export function createBlockImageViewElement( writer, slotElement ) { - const emptyElement = writer.createEmptyElement( 'img' ); - const container = writer.createContainerElement( 'figure', { class: 'image' } ); - - writer.insert( writer.createPositionAt( container, 0 ), emptyElement ); - writer.insert( writer.createPositionAt( container, 1 ), slotElement ); - - return container; + return writer.createContainerElement( 'figure', { class: 'image' }, [ + writer.createEmptyElement( 'img' ), + slotElement + ] ); } /** diff --git a/packages/ckeditor5-media-embed/src/utils.js b/packages/ckeditor5-media-embed/src/utils.js index b166123832d..01d46b6c2b2 100644 --- a/packages/ckeditor5-media-embed/src/utils.js +++ b/packages/ckeditor5-media-embed/src/utils.js @@ -74,12 +74,10 @@ export function isMediaWidget( viewElement ) { * @returns {module:engine/view/containerelement~ContainerElement} */ export function createMediaFigureElement( { writer, slotFor }, registry, url, options ) { - const figure = writer.createContainerElement( 'figure', { class: 'media' } ); - - writer.insert( writer.createPositionAt( figure, 0 ), registry.getMediaViewElement( writer, url, options ) ); - writer.insert( writer.createPositionAt( figure, 1 ), slotFor( 'children' ) ); - - return figure; + return writer.createContainerElement( 'figure', { class: 'media' }, [ + registry.getMediaViewElement( writer, url, options ), + slotFor( 'children' ) + ] ); } /** diff --git a/packages/ckeditor5-page-break/src/pagebreakediting.js b/packages/ckeditor5-page-break/src/pagebreakediting.js index 4d60e3cc5db..fd71083dbcd 100644 --- a/packages/ckeditor5-page-break/src/pagebreakediting.js +++ b/packages/ckeditor5-page-break/src/pagebreakediting.js @@ -48,15 +48,13 @@ export default class PageBreakEditing extends Plugin { class: 'page-break', // If user has no `.ck-content` styles, it should always break a page during print. style: 'page-break-after: always' - } ); - - // For a rationale of using span inside a div see: - // https://github.com/ckeditor/ckeditor5-page-break/pull/1#discussion_r328934062. - const spanElement = writer.createContainerElement( 'span', { - style: 'display: none' - } ); - - writer.insert( writer.createPositionAt( divElement, 0 ), spanElement ); + }, [ + // For a rationale of using span inside a div see: + // https://github.com/ckeditor/ckeditor5-page-break/pull/1#discussion_r328934062. + writer.createContainerElement( 'span', { + style: 'display: none' + } ) + ] ); return divElement; } diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index dd1f668dd49..a6384e62101 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -20,39 +20,34 @@ import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget'; */ export function downcastTable( tableUtils, options = {} ) { return ( table, { writer, slotFor } ) => { - const figureElement = writer.createContainerElement( 'figure', { class: 'table' } ); - const tableElement = writer.createContainerElement( 'table' ); - - writer.insert( writer.createPositionAt( figureElement, 0 ), tableElement ); - const headingRows = table.getAttribute( 'headingRows' ) || 0; + const tableSections = []; // Table head slot. if ( headingRows > 0 ) { - const tableHead = writer.createContainerElement( 'thead' ); - const headSlot = slotFor( - element => element.is( 'element', 'tableRow' ) && element.index < headingRows + tableSections.push( + writer.createContainerElement( 'thead', null, + slotFor( element => element.is( 'element', 'tableRow' ) && element.index < headingRows ) + ) ); - - writer.insert( writer.createPositionAt( tableElement, 'end' ), tableHead ); - writer.insert( writer.createPositionAt( tableHead, 0 ), headSlot ); } // Table body slot. if ( headingRows < tableUtils.getRows( table ) ) { - const tableBody = writer.createContainerElement( 'tbody' ); - const bodySlot = slotFor( - element => element.is( 'element', 'tableRow' ) && element.index >= headingRows + tableSections.push( + writer.createContainerElement( 'tbody', null, + slotFor( element => element.is( 'element', 'tableRow' ) && element.index >= headingRows ) + ) ); - - writer.insert( writer.createPositionAt( tableElement, 'end' ), tableBody ); - writer.insert( writer.createPositionAt( tableBody, 0 ), bodySlot ); } - // Slot for the rest (for example caption). - const restSlot = slotFor( element => !element.is( 'element', 'tableRow' ) ); + const figureElement = writer.createContainerElement( 'figure', { class: 'table' }, [ + // Table with proper sections (thead, tbody). + writer.createContainerElement( 'table', null, tableSections ), - writer.insert( writer.createPositionAt( figureElement, 'end' ), restSlot ); + // Slot for the rest (for example caption). + slotFor( element => !element.is( 'element', 'tableRow' ) ) + ] ); return options.asWidget ? toTableWidget( figureElement, writer ) : figureElement; }; From 20e34bcd2e20a353983e8f693ca8da62d48767a1 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 18 Oct 2021 16:30:29 +0200 Subject: [PATCH 091/140] Added tests. --- .../tests/view/downcastwriter/writer.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js b/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js index fa50e36880f..9e91c79a41f 100644 --- a/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js +++ b/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js @@ -138,6 +138,7 @@ describe( 'DowncastWriter', () => { expect( element.name ).to.equal( 'foo' ); expect( element.isAllowedInsideAttributeElement ).to.be.false; assertElementAttributes( element, attributes ); + expect( element.childCount ).to.equal( 0 ); } ); it( 'should allow to pass additional options', () => { @@ -148,6 +149,54 @@ describe( 'DowncastWriter', () => { expect( element.isAllowedInsideAttributeElement ).to.be.true; assertElementAttributes( element, attributes ); } ); + + it( 'should create element without attributes', () => { + const element = writer.createContainerElement( 'foo', null ); + + expect( element.is( 'containerElement' ) ).to.be.true; + expect( element.name ).to.equal( 'foo' ); + expect( element.isAllowedInsideAttributeElement ).to.be.false; + expect( Array.from( element.getAttributes() ).length ).to.equal( 0 ); + expect( element.childCount ).to.equal( 0 ); + } ); + + it( 'should create element with single child', () => { + const child = writer.createEmptyElement( 'bar' ); + const element = writer.createContainerElement( 'foo', null, child ); + + expect( element.is( 'containerElement' ) ).to.be.true; + expect( element.name ).to.equal( 'foo' ); + expect( element.isAllowedInsideAttributeElement ).to.be.false; + expect( Array.from( element.getAttributes() ).length ).to.equal( 0 ); + expect( element.childCount ).to.equal( 1 ); + expect( element.getChild( 0 ) ).to.equal( child ); + } ); + + it( 'should create element with children and attributes', () => { + const first = writer.createEmptyElement( 'aaa' ); + const second = writer.createEmptyElement( 'bbb' ); + const element = writer.createContainerElement( 'foo', attributes, [ first, second ] ); + + expect( element.is( 'containerElement' ) ).to.be.true; + expect( element.name ).to.equal( 'foo' ); + expect( element.isAllowedInsideAttributeElement ).to.be.false; + assertElementAttributes( element, attributes ); + expect( element.childCount ).to.equal( 2 ); + expect( element.getChild( 0 ) ).to.equal( first ); + expect( element.getChild( 1 ) ).to.equal( second ); + } ); + + it( 'should create element with children attributes and allow additional options', () => { + const child = writer.createEmptyElement( 'bar' ); + const element = writer.createContainerElement( 'foo', attributes, { isAllowedInsideAttributeElement: true }, child ); + + expect( element.is( 'containerElement' ) ).to.be.true; + expect( element.name ).to.equal( 'foo' ); + expect( element.isAllowedInsideAttributeElement ).to.be.true; + assertElementAttributes( element, attributes ); + expect( element.childCount ).to.equal( 1 ); + expect( element.getChild( 0 ) ).to.equal( child ); + } ); } ); describe( 'createEditableElement()', () => { From 4bf669269a9e2b63c689c02ebf6b22be440c29e6 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 20 Oct 2021 18:11:48 +0200 Subject: [PATCH 092/140] Reordered params of DowncastWriter#createContainerElement(). --- .../ckeditor5-engine/src/view/downcastwriter.js | 15 +++++++++------ .../tests/view/downcastwriter/writer.js | 2 +- packages/ckeditor5-html-support/src/converters.js | 6 ++++-- packages/ckeditor5-image/src/image/converters.js | 5 +++-- packages/ckeditor5-image/src/image/utils.js | 7 ++++--- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-engine/src/view/downcastwriter.js b/packages/ckeditor5-engine/src/view/downcastwriter.js index 19fe71a9233..9a53bdeba2e 100644 --- a/packages/ckeditor5-engine/src/view/downcastwriter.js +++ b/packages/ckeditor5-engine/src/view/downcastwriter.js @@ -218,18 +218,21 @@ export default class DowncastWriter { * * @param {String} name Name of the element. * @param {Object} [attributes] Elements attributes. + * @param {module:engine/view/node~Node|Iterable.} [childrenOrOptions] + * A list of nodes to be inserted into created element. * @param {Object} [options] Element's options. * @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}. - * @param {module:engine/view/node~Node|Iterable.} [children] - * A list of nodes to be inserted into created element. * @returns {module:engine/view/containerelement~ContainerElement} Created element. */ - createContainerElement( name, attributes, options = {}, children = null ) { - if ( !isPlainObject( options ) ) { - children = options; - options = {}; + createContainerElement( name, attributes, childrenOrOptions = {}, options = {} ) { + let children = null; + + if ( isPlainObject( childrenOrOptions ) ) { + options = childrenOrOptions; + } else { + children = childrenOrOptions; } const containerElement = new ContainerElement( this.document, name, attributes, children ); diff --git a/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js b/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js index 9e91c79a41f..58d9c7d8125 100644 --- a/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js +++ b/packages/ckeditor5-engine/tests/view/downcastwriter/writer.js @@ -188,7 +188,7 @@ describe( 'DowncastWriter', () => { it( 'should create element with children attributes and allow additional options', () => { const child = writer.createEmptyElement( 'bar' ); - const element = writer.createContainerElement( 'foo', attributes, { isAllowedInsideAttributeElement: true }, child ); + const element = writer.createContainerElement( 'foo', attributes, child, { isAllowedInsideAttributeElement: true } ); expect( element.is( 'containerElement' ) ).to.be.true; expect( element.name ).to.equal( 'foo' ); diff --git a/packages/ckeditor5-html-support/src/converters.js b/packages/ckeditor5-html-support/src/converters.js index ee8a9296e81..4df0dbc07a7 100644 --- a/packages/ckeditor5-html-support/src/converters.js +++ b/packages/ckeditor5-html-support/src/converters.js @@ -53,9 +53,11 @@ export function toObjectWidgetConverter( editor, { view: viewName, isInline } ) const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div', { class: 'html-object-embed', 'data-html-object-embed-label': widgetLabel - }, { + }, [ + viewElement + ], { isAllowedInsideAttributeElement: isInline - }, viewElement ); + } ); return toWidget( viewContainer, writer, { widgetLabel } ); }; diff --git a/packages/ckeditor5-image/src/image/converters.js b/packages/ckeditor5-image/src/image/converters.js index a1311d61e53..71dec660848 100644 --- a/packages/ckeditor5-image/src/image/converters.js +++ b/packages/ckeditor5-image/src/image/converters.js @@ -232,10 +232,11 @@ export function downcastSourcesAttribute( imageUtils ) { if ( data.attributeNewValue && data.attributeNewValue.length ) { // Make sure does not break attribute elements, for instance in linked images. - const pictureElement = viewWriter.createContainerElement( 'picture', {}, { isAllowedInsideAttributeElement: true }, + const pictureElement = viewWriter.createContainerElement( 'picture', null, data.attributeNewValue.map( sourceAttributes => { return viewWriter.createEmptyElement( 'source', sourceAttributes ); - } ) + } ), + { isAllowedInsideAttributeElement: true } ); // Collect all wrapping attribute elements. diff --git a/packages/ckeditor5-image/src/image/utils.js b/packages/ckeditor5-image/src/image/utils.js index 96b225583d0..1daad405c34 100644 --- a/packages/ckeditor5-image/src/image/utils.js +++ b/packages/ckeditor5-image/src/image/utils.js @@ -21,9 +21,10 @@ import { first } from 'ckeditor5/src/utils'; * @returns {module:engine/view/containerelement~ContainerElement} */ export function createInlineImageViewElement( writer ) { - return writer.createContainerElement( 'span', { class: 'image-inline' }, { isAllowedInsideAttributeElement: true }, [ - writer.createEmptyElement( 'img' ) - ] ); + return writer.createContainerElement( 'span', { class: 'image-inline' }, + writer.createEmptyElement( 'img' ), + { isAllowedInsideAttributeElement: true } + ); } /** From 60de348c9b9291de26a7fd54856879ad1a4101ea Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 22 Oct 2021 14:19:37 +0200 Subject: [PATCH 093/140] Docs. --- .../ckeditor5-engine/src/view/downcastwriter.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/view/downcastwriter.js b/packages/ckeditor5-engine/src/view/downcastwriter.js index 9a53bdeba2e..757fb996153 100644 --- a/packages/ckeditor5-engine/src/view/downcastwriter.js +++ b/packages/ckeditor5-engine/src/view/downcastwriter.js @@ -216,10 +216,20 @@ export default class DowncastWriter { * // Create element with custom classes. * writer.createContainerElement( 'p', { class: 'foo bar baz' } ); * + * // Create element with children. + * writer.createContainerElement( 'figure', { class: 'image' }, [ + * writer.createEmptyElement( 'img' ), + * writer.createContainerElement( 'figcaption' ) + * ] ); + * + * // Create element with specific options. + * writer.createContainerElement( 'span', { class: 'placeholder' }, { isAllowedInsideAttributeElement: true } ); + * * @param {String} name Name of the element. * @param {Object} [attributes] Elements attributes. - * @param {module:engine/view/node~Node|Iterable.} [childrenOrOptions] - * A list of nodes to be inserted into created element. + * @param {module:engine/view/node~Node|Iterable.|Object} [childrenOrOptions] + * A node or a list of nodes to be inserted into the created element. If no children were specified, element's `options` + * can be passed in this argument. * @param {Object} [options] Element's options. * @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped From 78899afef221bde12b056a03fb7244bc8e5d6130 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 22 Oct 2021 14:23:40 +0200 Subject: [PATCH 094/140] Code refactoring. --- .../ckeditor5-html-support/src/converters.js | 18 ++++++++++-------- .../src/pagebreakediting.js | 13 +++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-html-support/src/converters.js b/packages/ckeditor5-html-support/src/converters.js index 4df0dbc07a7..ea365381780 100644 --- a/packages/ckeditor5-html-support/src/converters.js +++ b/packages/ckeditor5-html-support/src/converters.js @@ -50,14 +50,16 @@ export function toObjectWidgetConverter( editor, { view: viewName, isInline } ) // Widget cannot be a raw element because the widget system would not be able // to add its UI to it. Thus, we need separate view container. - const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div', { - class: 'html-object-embed', - 'data-html-object-embed-label': widgetLabel - }, [ - viewElement - ], { - isAllowedInsideAttributeElement: isInline - } ); + const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div', + { + class: 'html-object-embed', + 'data-html-object-embed-label': widgetLabel + }, + viewElement, + { + isAllowedInsideAttributeElement: isInline + } + ); return toWidget( viewContainer, writer, { widgetLabel } ); }; diff --git a/packages/ckeditor5-page-break/src/pagebreakediting.js b/packages/ckeditor5-page-break/src/pagebreakediting.js index fd71083dbcd..a3c98031299 100644 --- a/packages/ckeditor5-page-break/src/pagebreakediting.js +++ b/packages/ckeditor5-page-break/src/pagebreakediting.js @@ -44,17 +44,18 @@ export default class PageBreakEditing extends Plugin { conversion.for( 'dataDowncast' ).elementToStructure( { model: 'pageBreak', view: ( modelElement, { writer } ) => { - const divElement = writer.createContainerElement( 'div', { - class: 'page-break', - // If user has no `.ck-content` styles, it should always break a page during print. - style: 'page-break-after: always' - }, [ + const divElement = writer.createContainerElement( 'div', + { + class: 'page-break', + // If user has no `.ck-content` styles, it should always break a page during print. + style: 'page-break-after: always' + }, // For a rationale of using span inside a div see: // https://github.com/ckeditor/ckeditor5-page-break/pull/1#discussion_r328934062. writer.createContainerElement( 'span', { style: 'display: none' } ) - ] ); + ); return divElement; } From 05201a0672ab94ba00fdbe79dd416932a6ca501c Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 11:13:54 +0200 Subject: [PATCH 095/140] Implemented EditingController#reconvertMarker|Item. --- .../src/controller/editingcontroller.js | 31 ++++++++++ packages/ckeditor5-engine/src/model/differ.js | 57 ++++++++++--------- packages/ckeditor5-engine/src/model/writer.js | 2 +- .../tests/conversion/downcasthelpers.js | 16 +++--- .../ckeditor5-engine/tests/model/differ.js | 10 ++-- .../tests/imageupload/imageuploadprogress.js | 4 +- .../converters/table-cell-refresh-handler.js | 8 +-- .../table-headings-refresh-handler.js | 8 +-- packages/ckeditor5-table/src/tableediting.js | 4 +- 9 files changed, 86 insertions(+), 54 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/editingcontroller.js b/packages/ckeditor5-engine/src/controller/editingcontroller.js index dc24a4f4f9f..87fa627a8b0 100644 --- a/packages/ckeditor5-engine/src/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/src/controller/editingcontroller.js @@ -22,6 +22,7 @@ import { import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import { convertSelectionChange } from '../conversion/upcasthelpers'; // @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' ); @@ -152,6 +153,36 @@ export default class EditingController { this.view.destroy(); this.stopListening(); } + + /** + * TODO + */ + reconvertMarker( markerOrName ) { + const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name; + const currentMarker = this.model.markers.get( markerName ); + + if ( !currentMarker ) { + /** + * Marker with provided name does not exists. + * + * @error editingcontroller-reconvertmarker-marker-not-exists + */ + throw new CKEditorError( 'editingcontroller-reconvertmarker-marker-not-exists', this ); + } + + this.model.change( () => { + this.model.markers._refresh( currentMarker ); + } ); + } + + /** + * TODO + */ + reconvertItem( item ) { + this.model.change( () => { + this.model.document.differ._refreshItem( item ); + } ); + } } mix( EditingController, ObservableMixin ); diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 33f157d9d77..8ae2417235e 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -118,34 +118,6 @@ export default class Differ { return this._changesInElement.size == 0 && this._changedMarkers.size == 0; } - /** - * Marks given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted in the differ changes - * set, so it will be effectively re-converted when differ changes will be handled by a dispatcher. - * - * @param {module:engine/model/item~Item} item Item to refresh. - */ - refreshItem( item ) { - if ( this._isInInsertedElement( item.parent ) ) { - return; - } - - this._markRemove( item.parent, item.startOffset, item.offsetSize ); - this._markInsert( item.parent, item.startOffset, item.offsetSize ); - - this._refreshedItems.add( item ); - - const range = Range._createOn( item ); - - for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) { - const markerRange = marker.getRange(); - - this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData ); - } - - // Clear cache after each buffered operation as it is no longer valid. - this._cachedChanges = null; - } - /** * Buffers the given operation. An operation has to be buffered before it is executed. * @@ -580,6 +552,35 @@ export default class Differ { this._cachedChanges = null; } + /** + * Marks given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted in the differ changes + * set, so it will be effectively re-converted when differ changes will be handled by a dispatcher. + * + * @protected + * @param {module:engine/model/item~Item} item Item to refresh. + */ + _refreshItem( item ) { + if ( this._isInInsertedElement( item.parent ) ) { + return; + } + + this._markRemove( item.parent, item.startOffset, item.offsetSize ); + this._markInsert( item.parent, item.startOffset, item.offsetSize ); + + this._refreshedItems.add( item ); + + const range = Range._createOn( item ); + + for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) { + const markerRange = marker.getRange(); + + this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData ); + } + + // Clear cache after each buffered operation as it is no longer valid. + this._cachedChanges = null; + } + /** * Saves and handles an insert change. * diff --git a/packages/ckeditor5-engine/src/model/writer.js b/packages/ckeditor5-engine/src/model/writer.js index db15a3e052e..b00fea914a1 100644 --- a/packages/ckeditor5-engine/src/model/writer.js +++ b/packages/ckeditor5-engine/src/model/writer.js @@ -1047,7 +1047,7 @@ export default class Writer { if ( !options ) { this.model.markers._refresh( currentMarker ); - return; + throw 'ojezu'; } const hasUsingOperationDefined = typeof options.usingOperation == 'boolean'; diff --git a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js index ac939fce8c3..31ed7fdfcf4 100644 --- a/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js +++ b/packages/ckeditor5-engine/tests/conversion/downcasthelpers.js @@ -383,7 +383,7 @@ describe( 'DowncastHelpers', () => { expect( textAfter ).to.equal( textBefore ); } ); - it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + it( 'should not reuse child view element if marked by Differ#_refreshItem()', () => { model.schema.register( 'paragraph', { inheritAllFrom: '$block', allowIn: 'simpleBlock' } ); downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } ); @@ -393,7 +393,7 @@ describe( 'DowncastHelpers', () => { model.change( writer => { writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + model.document.differ._refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); } ); const [ viewAfter, paraAfter, textAfter ] = getNodes(); @@ -756,14 +756,14 @@ describe( 'DowncastHelpers', () => { expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); } ); - it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + it( 'should not reuse child view element if marked by Differ#_refreshItem()', () => { setModelData( model, 'foo' ); const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + model.document.differ._refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); } ); const [ viewAfter, paraAfter, textAfter ] = getNodes(); @@ -1306,14 +1306,14 @@ describe( 'DowncastHelpers', () => { expect( textAfter, 'text' ).to.equal( textBefore ); } ); - it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + it( 'should not reuse child view element if marked by Differ#_refreshItem()', () => { setModelData( model, 'foo' ); const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + model.document.differ._refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); } ); const [ viewAfter, paraAfter, textAfter ] = getNodes(); @@ -1584,14 +1584,14 @@ describe( 'DowncastHelpers', () => { expect( textAfterAfter1, 'text' ).to.equal( textAfter1 ); } ); - it( 'should not reuse child view element if marked by Differ#refreshItem()', () => { + it( 'should not reuse child view element if marked by Differ#_refreshItem()', () => { setModelData( model, 'foo' ); const [ viewBefore, paraBefore, textBefore ] = getNodes(); model.change( writer => { writer.setAttribute( 'toStyle', 'display:inline', modelRoot.getChild( 0 ) ); - model.document.differ.refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); + model.document.differ._refreshItem( modelRoot.getChild( 0 ).getChild( 0 ) ); } ); const [ viewAfter, paraAfter, textAfter ] = getNodes(); diff --git a/packages/ckeditor5-engine/tests/model/differ.js b/packages/ckeditor5-engine/tests/model/differ.js index a3e7baf5c69..0e6a0c9f5bd 100644 --- a/packages/ckeditor5-engine/tests/model/differ.js +++ b/packages/ckeditor5-engine/tests/model/differ.js @@ -1791,11 +1791,11 @@ describe( 'Differ', () => { } ); } ); - describe( 'refreshItem()', () => { + describe( '#_refreshItem()', () => { it( 'should mark given element to be removed and added again', () => { const p = root.getChild( 0 ); - differ.refreshItem( p ); + differ._refreshItem( p ); expectChanges( [ { type: 'remove', name: 'paragraph', length: 1, position: model.createPositionBefore( p ) }, @@ -1808,7 +1808,7 @@ describe( 'Differ', () => { const range = model.createRangeIn( p ); const textProxy = [ ...range.getItems() ][ 0 ]; - differ.refreshItem( textProxy ); + differ._refreshItem( textProxy ); expectChanges( [ { type: 'remove', name: '$text', length: 3, position: model.createPositionAt( p, 0 ) }, @@ -1821,7 +1821,7 @@ describe( 'Differ', () => { model.change( () => { insert( new Element( 'blockQuote', null, new Element( 'paragraph' ) ), new Position( root, [ 2 ] ) ); - differ.refreshItem( root.getChild( 2 ).getChild( 0 ) ); + differ._refreshItem( root.getChild( 2 ).getChild( 0 ) ); expectChanges( [ { type: 'insert', name: 'blockQuote', length: 1, position: new Position( root, [ 2 ] ) } @@ -1846,7 +1846,7 @@ describe( 'Differ', () => { const markersToRefresh = [ 'markerA', 'markerB', 'markerC' ]; - differ.refreshItem( root.getChild( 1 ) ); + differ._refreshItem( root.getChild( 1 ) ); expectChanges( [ { type: 'remove', name: 'paragraph', length: 1, position: new Position( root, [ 1 ] ) }, diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadprogress.js b/packages/ckeditor5-image/tests/imageupload/imageuploadprogress.js index 78a86132332..fcc7f57fa73 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadprogress.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadprogress.js @@ -126,9 +126,9 @@ describe( 'ImageUploadProgress', () => { model.document.registerPostFixer( () => { for ( const change of doc.differ.getChanges() ) { - // The differ.refreshItem() simulates remove and insert of and image parent thus preventing image from proper work. + // The editing.reconvertItem() simulates remove and insert of and image parent thus preventing image from proper work. if ( change.type == 'insert' && change.name == 'imageBlock' ) { - doc.differ.refreshItem( change.position.parent ); + editor.editing.reconvertItem( change.position.parent ); return false; // Refreshing item should not trigger calling post-fixer again. } diff --git a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js index 9d79b7e7442..0052dd2a527 100644 --- a/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-cell-refresh-handler.js @@ -19,9 +19,9 @@ import { isSingleParagraphWithoutAttributes } from './downcast'; * re-rendered so it changes from `` to `

`. The easiest way to do it is to re-render the entire table cell. * * @param {module:engine/model/model~Model} model - * @param {module:engine/conversion/mapper~Mapper} mapper + * @param {module:engine/controller/editingcontroller~EditingController} editing */ -export default function tableCellRefreshHandler( model, mapper ) { +export default function tableCellRefreshHandler( model, editing ) { const differ = model.document.differ; // Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes. @@ -36,10 +36,10 @@ export default function tableCellRefreshHandler( model, mapper ) { } for ( const tableCell of cellsToCheck.values() ) { - const paragraphsToRefresh = Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, mapper ) ); + const paragraphsToRefresh = Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, editing.mapper ) ); for ( const paragraph of paragraphsToRefresh ) { - differ.refreshItem( paragraph ); + editing.reconvertItem( paragraph ); } } } diff --git a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js index ccfe8e99574..c397dfdb442 100644 --- a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js @@ -18,9 +18,9 @@ import TableWalker from '../tablewalker'; * When table headings attribute changes, all the cells/rows are marked to re-render to change between `` and ``. * * @param {module:engine/model/model~Model} model - * @param {module:engine/conversion/mapper~Mapper} mapper + * @param {module:engine/controller/editingcontroller~EditingController} editing */ -export default function tableHeadingsRefreshHandler( model, mapper ) { +export default function tableHeadingsRefreshHandler( model, editing ) { const differ = model.document.differ; for ( const change of differ.getChanges() ) { @@ -58,10 +58,10 @@ export default function tableHeadingsRefreshHandler( model, mapper ) { const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns; const expectedElementName = isHeading ? 'th' : 'td'; - const viewElement = mapper.toViewElement( tableSlot.cell ); + const viewElement = editing.mapper.toViewElement( tableSlot.cell ); if ( viewElement && viewElement.is( 'element' ) && viewElement.name != expectedElementName ) { - differ.refreshItem( isRowChange ? tableSlot.cell.parent : tableSlot.cell ); + editing.reconvertItem( isRowChange ? tableSlot.cell.parent : tableSlot.cell ); } } } diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js index f4b26abab43..72799cc69ee 100644 --- a/packages/ckeditor5-table/src/tableediting.js +++ b/packages/ckeditor5-table/src/tableediting.js @@ -193,8 +193,8 @@ export default class TableEditing extends Plugin { injectTableCellParagraphPostFixer( model ); this.listenTo( model.document, 'change:data', () => { - tableHeadingsRefreshHandler( model, editor.editing.mapper ); - tableCellRefreshHandler( model, editor.editing.mapper ); + tableHeadingsRefreshHandler( model, editor.editing ); + tableCellRefreshHandler( model, editor.editing ); } ); } } From 80fb1e4a36073060e5178e26037d6fea7d55bf76 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 11:30:50 +0200 Subject: [PATCH 096/140] Log instead of throwing. --- packages/ckeditor5-engine/src/model/writer.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/writer.js b/packages/ckeditor5-engine/src/model/writer.js index b00fea914a1..b231044d636 100644 --- a/packages/ckeditor5-engine/src/model/writer.js +++ b/packages/ckeditor5-engine/src/model/writer.js @@ -27,7 +27,7 @@ import DocumentSelection from './documentselection'; import toMap from '@ckeditor/ckeditor5-utils/src/tomap'; -import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; +import CKEditorError, { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; /** * The model can only be modified by using the writer. It should be used whenever you want to create a node, modify @@ -1047,7 +1047,14 @@ export default class Writer { if ( !options ) { this.model.markers._refresh( currentMarker ); - throw 'ojezu'; + /** + * TODO + * + * @error writer-TODO + */ + logWarning( 'writer-TODO' ); + + return; } const hasUsingOperationDefined = typeof options.usingOperation == 'boolean'; From 6045a524cd9b362f76b7c00aa435139d036febbc Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 16:35:47 +0200 Subject: [PATCH 097/140] Docs. --- .../src/controller/editingcontroller.js | 47 +++++++++++++++++-- packages/ckeditor5-engine/src/model/writer.js | 42 +++++------------ 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/packages/ckeditor5-engine/src/controller/editingcontroller.js b/packages/ckeditor5-engine/src/controller/editingcontroller.js index 87fa627a8b0..c62f236fd9d 100644 --- a/packages/ckeditor5-engine/src/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/src/controller/editingcontroller.js @@ -155,7 +155,37 @@ export default class EditingController { } /** - * TODO + * Calling this method will refresh the marker by triggering the downcast conversion for it. + * + * Reconverting the marker is useful when you want to change its {@link module:engine/view/element~Element view element} + * without changing any marker data. For instance: + * + * let isCommentActive = false; + * + * model.conversion.markerToHighlight( { + * model: 'comment', + * view: data => { + * const classes = [ 'comment-marker' ]; + * + * if ( isCommentActive ) { + * classes.push( 'comment-marker--active' ); + * } + * + * return { classes }; + * } + * } ); + * + * // ... + * + * // Change the property that indicates if marker is displayed as active or not. + * isCommentActive = true; + * + * // Reconverting will downcast and synchronize the marker with the new isCommentActive state value. + * editor.editing.reconvertMarker( 'comment' ); + * + * **Note**: If you want to reconvert a model item, use {@link #reconvertItem} instead. + * + * @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance. */ reconvertMarker( markerOrName ) { const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name; @@ -163,11 +193,11 @@ export default class EditingController { if ( !currentMarker ) { /** - * Marker with provided name does not exists. + * The marker with provided name does not exist and cannot be reconverted. * - * @error editingcontroller-reconvertmarker-marker-not-exists + * @error editingcontroller-reconvertmarker-marker-not-exist */ - throw new CKEditorError( 'editingcontroller-reconvertmarker-marker-not-exists', this ); + throw new CKEditorError( 'editingcontroller-reconvertmarker-marker-not-exist', this, { markerName } ); } this.model.change( () => { @@ -176,7 +206,14 @@ export default class EditingController { } /** - * TODO + * Calling this method will downcast a model item on demand (by requesting a refresh in the {@link module:engine/model/differ~Differ}). + * + * You can use it if you want the view representation of a specific item updated as a response to external modifications. For instance, + * when the view structure depends not only on the associated model data but also on some external state. + * + * **Note**: If you want to reconvert a model marker, use {@link #reconvertMarker} instead. + * + * @param {module:engine/model/item~Item} item Item to refresh. */ reconvertItem( item ) { this.model.change( () => { diff --git a/packages/ckeditor5-engine/src/model/writer.js b/packages/ckeditor5-engine/src/model/writer.js index b231044d636..f1cd50d2bb7 100644 --- a/packages/ckeditor5-engine/src/model/writer.js +++ b/packages/ckeditor5-engine/src/model/writer.js @@ -968,30 +968,8 @@ export default class Writer { * As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique * name is created and returned. * - * As the second parameter you can set the new marker data or leave this parameter as empty which will just refresh - * the marker by triggering downcast conversion for it. Refreshing the marker is useful when you want to change - * the marker {@link module:engine/view/element~Element view element} without changing any marker data. - * - * let isCommentActive = false; - * - * model.conversion.markerToHighlight( { - * model: 'comment', - * view: data => { - * const classes = [ 'comment-marker' ]; - * - * if ( isCommentActive ) { - * classes.push( 'comment-marker--active' ); - * } - * - * return { classes }; - * } - * } ); - * - * // Change the property that indicates if marker is displayed as active or not. - * isCommentActive = true; - * - * // And refresh the marker to convert it with additional class. - * model.change( writer => writer.updateMarker( 'comment' ) ); + * **Note**: If you want to change the {@link module:engine/view/element~Element view element} of the marker while its data in the model + * remains the same, use the dedicated {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker} method. * * The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See * {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between @@ -1037,7 +1015,7 @@ export default class Writer { if ( !currentMarker ) { /** - * Marker with provided name does not exists. + * Marker with provided name does not exist and will not be updated. * * @error writer-updatemarker-marker-not-exists */ @@ -1045,14 +1023,18 @@ export default class Writer { } if ( !options ) { - this.model.markers._refresh( currentMarker ); - /** - * TODO + * Usage of `writer.updateMarker()` only to reconvert (refresh) a + * {@link module:engine/model/markercollection~Marker model marker} was deprecated and may not work in the future. + * Please update your code to use + * {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker `editor.editing.reconvertMarker()`} + * instead. * - * @error writer-TODO + * @error writer-updatemarker-reconvert-using-editingcontroller */ - logWarning( 'writer-TODO' ); + logWarning( 'writer-updatemarker-reconvert-using-editingcontroller', { markerName } ); + + this.model.markers._refresh( currentMarker ); return; } From 1e9f574eae63b1f6388cc74d60d1594bd3f67e65 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 16:40:27 +0200 Subject: [PATCH 098/140] Docs. --- packages/ckeditor5-engine/src/controller/editingcontroller.js | 1 + packages/ckeditor5-engine/src/model/writer.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ckeditor5-engine/src/controller/editingcontroller.js b/packages/ckeditor5-engine/src/controller/editingcontroller.js index c62f236fd9d..0d8f45b3a27 100644 --- a/packages/ckeditor5-engine/src/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/src/controller/editingcontroller.js @@ -196,6 +196,7 @@ export default class EditingController { * The marker with provided name does not exist and cannot be reconverted. * * @error editingcontroller-reconvertmarker-marker-not-exist + * @param {String} markerName The name of the reconverted marker. */ throw new CKEditorError( 'editingcontroller-reconvertmarker-marker-not-exist', this, { markerName } ); } diff --git a/packages/ckeditor5-engine/src/model/writer.js b/packages/ckeditor5-engine/src/model/writer.js index f1cd50d2bb7..d757ead60c4 100644 --- a/packages/ckeditor5-engine/src/model/writer.js +++ b/packages/ckeditor5-engine/src/model/writer.js @@ -1031,6 +1031,7 @@ export default class Writer { * instead. * * @error writer-updatemarker-reconvert-using-editingcontroller + * @param {String} markerName The name of the updated marker. */ logWarning( 'writer-updatemarker-reconvert-using-editingcontroller', { markerName } ); From a12391ce78f8e680d0836d68f1c5b4193fb0f3ad Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 17:11:50 +0200 Subject: [PATCH 099/140] Tests. --- .../tests/controller/editingcontroller.js | 115 ++++++++++++++++++ .../ckeditor5-engine/tests/model/writer.js | 21 +++- 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/tests/controller/editingcontroller.js b/packages/ckeditor5-engine/tests/controller/editingcontroller.js index 9bb35afcd5c..d8c8484b06c 100644 --- a/packages/ckeditor5-engine/tests/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/tests/controller/editingcontroller.js @@ -24,6 +24,8 @@ import { getData as getModelData, parse } from '../../src/dev-utils/model'; import { getData as getViewData } from '../../src/dev-utils/view'; import { StylesProcessor } from '../../src/view/stylesmap'; +import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + describe( 'EditingController', () => { describe( 'constructor()', () => { let model, editing; @@ -511,4 +513,117 @@ describe( 'EditingController', () => { expect( spy.called ).to.be.true; } ); } ); + + describe( 'reconvertMarker()', () => { + let model, editing; + + beforeEach( () => { + model = new Model(); + model.document.createRoot(); + editing = new EditingController( model, new StylesProcessor() ); + } ); + + it( 'should call MarkerCollection#_refresh()', () => { + model.change( writer => { + writer.insert( writer.createText( 'x' ), model.document.getRoot(), 0 ); + + writer.addMarker( 'foo', { + range: writer.createRangeIn( model.document.getRoot() ), + usingOperation: true + } ); + } ); + + const refreshSpy = sinon.stub( model.markers, '_refresh' ); + + editing.reconvertMarker( 'foo' ); + sinon.assert.calledOnce( refreshSpy ); + sinon.assert.calledWith( refreshSpy, model.markers.get( 'foo' ) ); + } ); + + it( 'should use a model.change() block to reconvert a marker', () => { + const changeSpy = sinon.spy(); + + model.change( writer => { + writer.insert( writer.createText( 'x' ), model.document.getRoot(), 0 ); + + writer.addMarker( 'foo', { + range: writer.createRangeIn( model.document.getRoot() ), + usingOperation: true + } ); + } ); + + model.document.on( 'change', changeSpy ); + sinon.assert.notCalled( changeSpy ); + + editing.reconvertMarker( 'foo' ); + sinon.assert.calledOnce( changeSpy ); + } ); + + it( 'should work when a marker instance was passed', () => { + let marker; + + model.change( writer => { + writer.insert( writer.createText( 'x' ), model.document.getRoot(), 0 ); + + marker = writer.addMarker( 'foo', { + range: writer.createRangeIn( model.document.getRoot() ), + usingOperation: true + } ); + } ); + + const refreshSpy = sinon.stub( model.markers, '_refresh' ); + + editing.reconvertMarker( marker ); + sinon.assert.calledOnce( refreshSpy ); + } ); + + it( 'should throw when marker was not found in the collection', () => { + expectToThrowCKEditorError( + () => { + editing.reconvertMarker( 'foo' ); + }, + 'editingcontroller-reconvertmarker-marker-not-exist', + editing, + { + markerName: 'foo' + } + ); + } ); + } ); + + describe( 'reconvertItem()', () => { + let model, editing; + + beforeEach( () => { + model = new Model(); + model.document.createRoot(); + editing = new EditingController( model, new StylesProcessor() ); + } ); + + it( 'should call Differ#_refreshItem()', () => { + model.change( writer => { + writer.insert( writer.createText( 'x' ), model.document.getRoot(), 0 ); + } ); + + const refreshSpy = sinon.stub( model.document.differ, '_refreshItem' ); + + editing.reconvertItem( model.document.getRoot().getChild( 0 ) ); + sinon.assert.calledOnce( refreshSpy ); + sinon.assert.calledWith( refreshSpy, model.document.getRoot().getChild( 0 ) ); + } ); + + it( 'should use a model.change() block to reconvert an item', () => { + const changeSpy = sinon.spy(); + + model.change( writer => { + writer.insert( writer.createText( 'x' ), model.document.getRoot(), 0 ); + } ); + + model.document.on( 'change', changeSpy ); + sinon.assert.notCalled( changeSpy ); + + editing.reconvertItem( model.document.getRoot().getChild( 0 ) ); + sinon.assert.calledOnce( changeSpy ); + } ); + } ); } ); diff --git a/packages/ckeditor5-engine/tests/model/writer.js b/packages/ckeditor5-engine/tests/model/writer.js index 5d1da842aa9..e348f307305 100644 --- a/packages/ckeditor5-engine/tests/model/writer.js +++ b/packages/ckeditor5-engine/tests/model/writer.js @@ -3,6 +3,8 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +/* global console */ + import Model from '../../src/model/model'; import Writer from '../../src/model/writer'; import Batch from '../../src/model/batch'; @@ -20,9 +22,13 @@ import { getNodesAndText } from '../../tests/model/_utils/utils'; import DocumentSelection from '../../src/model/documentselection'; import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + describe( 'Writer', () => { let model, doc, batch; + testUtils.createSinonSandbox(); + beforeEach( () => { model = new Model(); batch = new Batch(); @@ -2496,19 +2502,32 @@ describe( 'Writer', () => { }, 'writer-updatemarker-marker-not-exists', model ); } ); - it( 'should only refresh the marker when there is no provided options to update', () => { + it( 'should only refresh (but warn()) the marker when there is no provided options to update', () => { const marker = addMarker( 'name', { range, usingOperation: true } ); const spy = sinon.spy( model.markers, '_refresh' ); + const consoleWarnStub = testUtils.sinon.stub( console, 'warn' ); updateMarker( marker ); sinon.assert.calledOnce( spy ); sinon.assert.calledWithExactly( spy, marker ); + sinon.assert.calledOnce( consoleWarnStub ); + sinon.assert.calledWithExactly( consoleWarnStub.firstCall, + sinon.match( /^writer-updatemarker-reconvert-using-editingcontroller/ ), + { markerName: 'name' }, + sinon.match.string // Link to the documentation + ); updateMarker( 'name' ); sinon.assert.calledTwice( spy ); sinon.assert.calledWithExactly( spy.secondCall, marker ); + sinon.assert.calledTwice( consoleWarnStub ); + sinon.assert.calledWithExactly( consoleWarnStub.secondCall, + sinon.match( /^writer-updatemarker-reconvert-using-editingcontroller/ ), + { markerName: 'name' }, + sinon.match.string // Link to the documentation + ); } ); it( 'should throw when trying to use detached writer', () => { From 66c4cbed94d61032a8a8acf35957815ca403cfaf Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 29 Oct 2021 17:28:47 +0200 Subject: [PATCH 100/140] Docs. --- packages/ckeditor5-engine/src/model/differ.js | 2 +- packages/ckeditor5-engine/src/view/document.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js index 8ae2417235e..6b650203a09 100644 --- a/packages/ckeditor5-engine/src/model/differ.js +++ b/packages/ckeditor5-engine/src/model/differ.js @@ -100,7 +100,7 @@ export default class Differ { this._cachedChangesWithGraveyard = null; /** - * Set of model items that were marked to get refreshed in {@link #refreshItem}. + * Set of model items that were marked to get refreshed in {@link #_refreshItem}. * * @private * @type {Set.} diff --git a/packages/ckeditor5-engine/src/view/document.js b/packages/ckeditor5-engine/src/view/document.js index 16c35c71fc6..737dafbad55 100644 --- a/packages/ckeditor5-engine/src/view/document.js +++ b/packages/ckeditor5-engine/src/view/document.js @@ -141,7 +141,8 @@ export default class Document { * * * adding or removing attribute from elements, * * changes inside of {@link module:engine/view/uielement~UIElement UI elements}, - * * {@link module:engine/model/differ~Differ#refreshItem marking some of the model elements to be re-converted}. + * * {@link module:engine/controller/editingcontroller~EditingController#reconvertItem marking some of the model elements to be + * re-converted}. * * Try to avoid changes which touch view structure: * From af1d17742b62809b25936dd534ea8ece16849c70 Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 15 Nov 2021 11:49:50 +0100 Subject: [PATCH 101/140] Restructuring ckeditor5-list package (splitting into subdirectories). --- .../tests/alignmentediting.js | 2 +- .../ckeditor5-autoformat/tests/autoformat.js | 4 +-- .../tests/undointegration.js | 2 +- .../tests/blockquoteediting.js | 2 +- .../tests/model/operation/transform/utils.js | 2 +- .../tests/htmlcomment-integration.js | 6 ++-- .../tests/image/imageinlineediting.js | 2 +- packages/ckeditor5-indent/src/indent.js | 2 +- packages/ckeditor5-list/src/index.js | 12 ++++---- packages/ckeditor5-list/src/list.js | 8 ++--- .../src/{ => list}/converters.js | 8 ++--- .../src/{ => list}/indentcommand.js | 2 +- .../src/{ => list}/listcommand.js | 2 +- .../src/{ => list}/listediting.js | 2 +- .../ckeditor5-list/src/{ => list}/listui.js | 6 ++-- .../ckeditor5-list/src/{ => list}/utils.js | 2 +- packages/ckeditor5-list/src/liststyle.js | 8 ++--- .../src/{ => liststyle}/liststylecommand.js | 4 +-- .../src/{ => liststyle}/liststyleediting.js | 6 ++-- .../src/{ => liststyle}/liststyleui.js | 30 +++++++++---------- packages/ckeditor5-list/src/todolist.js | 8 ++--- .../{ => todolist}/checktodolistcommand.js | 4 +-- .../src/{ => todolist}/todolistconverters.js | 8 ++--- .../src/{ => todolist}/todolistediting.js | 8 ++--- .../src/{ => todolist}/todolistui.js | 6 ++-- packages/ckeditor5-list/tests/list.js | 4 +-- .../tests/{ => list}/indentcommand.js | 2 +- .../tests/{ => list}/listcommand.js | 2 +- .../tests/{ => list}/listediting.js | 6 ++-- .../ckeditor5-list/tests/{ => list}/listui.js | 4 +-- .../ckeditor5-list/tests/{ => list}/utils.js | 6 ++-- packages/ckeditor5-list/tests/liststyle.js | 4 +-- .../tests/{ => liststyle}/liststylecommand.js | 2 +- .../tests/{ => liststyle}/liststyleediting.js | 6 ++-- .../tests/{ => liststyle}/liststyleui.js | 28 ++++++++--------- packages/ckeditor5-list/tests/todolist.js | 4 +-- .../{ => todolist}/checktodolistcommand.js | 4 +-- .../tests/{ => todolist}/todolistediting.js | 8 ++--- .../tests/{ => todolist}/todolistui.js | 4 +-- .../tests/table-integration.js | 2 +- .../tests/tableclipboard-paste.js | 2 +- packages/ckeditor5-word-count/tests/utils.js | 2 +- .../ckeditor5-word-count/tests/wordcount.js | 2 +- 43 files changed, 119 insertions(+), 119 deletions(-) rename packages/ckeditor5-list/src/{ => list}/converters.js (99%) rename packages/ckeditor5-list/src/{ => list}/indentcommand.js (99%) rename packages/ckeditor5-list/src/{ => list}/listcommand.js (99%) rename packages/ckeditor5-list/src/{ => list}/listediting.js (99%) rename packages/ckeditor5-list/src/{ => list}/listui.js (85%) rename packages/ckeditor5-list/src/{ => list}/utils.js (99%) rename packages/ckeditor5-list/src/{ => liststyle}/liststylecommand.js (97%) rename packages/ckeditor5-list/src/{ => liststyle}/liststyleediting.js (99%) rename packages/ckeditor5-list/src/{ => liststyle}/liststyleui.js (86%) rename packages/ckeditor5-list/src/{ => todolist}/checktodolistcommand.js (95%) rename packages/ckeditor5-list/src/{ => todolist}/todolistconverters.js (97%) rename packages/ckeditor5-list/src/{ => todolist}/todolistediting.js (97%) rename packages/ckeditor5-list/src/{ => todolist}/todolistui.js (83%) rename packages/ckeditor5-list/tests/{ => list}/indentcommand.js (99%) rename packages/ckeditor5-list/tests/{ => list}/listcommand.js (99%) rename packages/ckeditor5-list/tests/{ => list}/listediting.js (99%) rename packages/ckeditor5-list/tests/{ => list}/listui.js (96%) rename packages/ckeditor5-list/tests/{ => list}/utils.js (99%) rename packages/ckeditor5-list/tests/{ => liststyle}/liststylecommand.js (99%) rename packages/ckeditor5-list/tests/{ => liststyle}/liststyleediting.js (99%) rename packages/ckeditor5-list/tests/{ => liststyle}/liststyleui.js (94%) rename packages/ckeditor5-list/tests/{ => todolist}/checktodolistcommand.js (98%) rename packages/ckeditor5-list/tests/{ => todolist}/todolistediting.js (99%) rename packages/ckeditor5-list/tests/{ => todolist}/todolistui.js (93%) diff --git a/packages/ckeditor5-alignment/tests/alignmentediting.js b/packages/ckeditor5-alignment/tests/alignmentediting.js index 440cfe49fc2..9ad6167b180 100644 --- a/packages/ckeditor5-alignment/tests/alignmentediting.js +++ b/packages/ckeditor5-alignment/tests/alignmentediting.js @@ -6,7 +6,7 @@ import AlignmentEditing from '../src/alignmentediting'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; diff --git a/packages/ckeditor5-autoformat/tests/autoformat.js b/packages/ckeditor5-autoformat/tests/autoformat.js index a9af44fcfca..1782bf68027 100644 --- a/packages/ckeditor5-autoformat/tests/autoformat.js +++ b/packages/ckeditor5-autoformat/tests/autoformat.js @@ -6,8 +6,8 @@ import Autoformat from '../src/autoformat'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; -import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; +import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolist/todolistediting'; import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import StrikethroughEditing from '@ckeditor/ckeditor5-basic-styles/src/strikethrough/strikethroughediting'; diff --git a/packages/ckeditor5-autoformat/tests/undointegration.js b/packages/ckeditor5-autoformat/tests/undointegration.js index dd294268633..447cdf2d84a 100644 --- a/packages/ckeditor5-autoformat/tests/undointegration.js +++ b/packages/ckeditor5-autoformat/tests/undointegration.js @@ -6,7 +6,7 @@ import Autoformat from '../src/autoformat'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import CodeEditing from '@ckeditor/ckeditor5-basic-styles/src/code/codeediting'; diff --git a/packages/ckeditor5-block-quote/tests/blockquoteediting.js b/packages/ckeditor5-block-quote/tests/blockquoteediting.js index 2697bb2f654..025361cd4ba 100644 --- a/packages/ckeditor5-block-quote/tests/blockquoteediting.js +++ b/packages/ckeditor5-block-quote/tests/blockquoteediting.js @@ -5,7 +5,7 @@ import BlockQuoteEditing from '../src/blockquoteediting'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; diff --git a/packages/ckeditor5-engine/tests/model/operation/transform/utils.js b/packages/ckeditor5-engine/tests/model/operation/transform/utils.js index 483faebad9b..4553f3783cc 100644 --- a/packages/ckeditor5-engine/tests/model/operation/transform/utils.js +++ b/packages/ckeditor5-engine/tests/model/operation/transform/utils.js @@ -5,7 +5,7 @@ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Typing from '@ckeditor/ckeditor5-typing/src/typing'; diff --git a/packages/ckeditor5-html-support/tests/htmlcomment-integration.js b/packages/ckeditor5-html-support/tests/htmlcomment-integration.js index ddf27c3fc76..8a714268d3e 100644 --- a/packages/ckeditor5-html-support/tests/htmlcomment-integration.js +++ b/packages/ckeditor5-html-support/tests/htmlcomment-integration.js @@ -29,9 +29,9 @@ import IndentEditing from '@ckeditor/ckeditor5-indent/src/indentediting'; import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting'; import LinkImageEditing from '@ckeditor/ckeditor5-link/src/linkimageediting'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; -import ListStyleEditing from '@ckeditor/ckeditor5-list/src/liststyleediting'; -import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; +import ListStyleEditing from '@ckeditor/ckeditor5-list/src/liststyle/liststyleediting'; +import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolist/todolistediting'; import MediaEmbedEditing from '@ckeditor/ckeditor5-media-embed/src/mediaembedediting'; diff --git a/packages/ckeditor5-image/tests/image/imageinlineediting.js b/packages/ckeditor5-image/tests/image/imageinlineediting.js index 88f43e46731..8032f68e4a3 100644 --- a/packages/ckeditor5-image/tests/image/imageinlineediting.js +++ b/packages/ckeditor5-image/tests/image/imageinlineediting.js @@ -11,7 +11,7 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import DataTransfer from '@ckeditor/ckeditor5-clipboard/src/datatransfer'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml'; diff --git a/packages/ckeditor5-indent/src/indent.js b/packages/ckeditor5-indent/src/indent.js index c05c7313d63..fd993b5c963 100644 --- a/packages/ckeditor5-indent/src/indent.js +++ b/packages/ckeditor5-indent/src/indent.js @@ -19,7 +19,7 @@ import IndentUI from './indentui'; * * The compatible features are: * - * * The {@link module:list/list~List} or {@link module:list/listediting~ListEditing} feature for list indentation. + * * The {@link module:list/list~List} or {@link module:list/list/listediting~ListEditing} feature for list indentation. * * The {@link module:indent/indentblock~IndentBlock} feature for block indentation. * * This is a "glue" plugin that loads the following plugins: diff --git a/packages/ckeditor5-list/src/index.js b/packages/ckeditor5-list/src/index.js index 0e5316e3a25..b221929c37c 100644 --- a/packages/ckeditor5-list/src/index.js +++ b/packages/ckeditor5-list/src/index.js @@ -8,11 +8,11 @@ */ export { default as List } from './list'; -export { default as ListEditing } from './listediting'; -export { default as ListUI } from './listui'; +export { default as ListEditing } from './list/listediting'; +export { default as ListUI } from './list/listui'; export { default as ListStyle } from './liststyle'; -export { default as ListStyleEditing } from './liststyleediting'; -export { default as ListStyleUI } from './liststyleui'; +export { default as ListStyleEditing } from './liststyle/liststyleediting'; +export { default as ListStyleUI } from './liststyle/liststyleui'; export { default as TodoList } from './todolist'; -export { default as TodoListEditing } from './todolistediting'; -export { default as TodoListUI } from './todolistui'; +export { default as TodoListEditing } from './todolist/todolistediting'; +export { default as TodoListUI } from './todolist/todolistui'; diff --git a/packages/ckeditor5-list/src/list.js b/packages/ckeditor5-list/src/list.js index b4a5e46e091..bb7812ff7d7 100644 --- a/packages/ckeditor5-list/src/list.js +++ b/packages/ckeditor5-list/src/list.js @@ -7,16 +7,16 @@ * @module list/list */ -import ListEditing from './listediting'; -import ListUI from './listui'; +import ListEditing from './list/listediting'; +import ListUI from './list/listui'; import { Plugin } from 'ckeditor5/src/core'; /** * The list feature. * - * This is a "glue" plugin that loads the {@link module:list/listediting~ListEditing list editing feature} - * and {@link module:list/listui~ListUI list UI feature}. + * This is a "glue" plugin that loads the {@link module:list/list/listediting~ListEditing list editing feature} + * and {@link module:list/list/listui~ListUI list UI feature}. * * @extends module:core/plugin~Plugin */ diff --git a/packages/ckeditor5-list/src/converters.js b/packages/ckeditor5-list/src/list/converters.js similarity index 99% rename from packages/ckeditor5-list/src/converters.js rename to packages/ckeditor5-list/src/list/converters.js index 31f38ff239f..d11ced9e554 100644 --- a/packages/ckeditor5-list/src/converters.js +++ b/packages/ckeditor5-list/src/list/converters.js @@ -4,7 +4,7 @@ */ /** - * @module list/converters + * @module list/list/converters */ import { TreeWalker } from 'ckeditor5/src/engine'; @@ -97,11 +97,11 @@ export function modelViewRemove( model ) { * A model-to-view converter for the `type` attribute change on the `listItem` model element. * * This change means that the `

  • ` element parent changes from `
      ` to `
        ` (or vice versa). This is accomplished - * by breaking view elements and changing their name. The next {@link module:list/converters~modelViewMergeAfterChangeType} + * by breaking view elements and changing their name. The next {@link module:list/list/converters~modelViewMergeAfterChangeType} * converter will attempt to merge split nodes. * * Splitting this conversion into 2 steps makes it possible to add an additional conversion in the middle. - * Check {@link module:list/todolistconverters~modelViewChangeType} to see an example of it. + * Check {@link module:list/todolist/todolistconverters~modelViewChangeType} to see an example of it. * * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event. @@ -130,7 +130,7 @@ export function modelViewChangeType( evt, data, conversionApi ) { } /** - * A model-to-view converter that attempts to merge nodes split by {@link module:list/converters~modelViewChangeType}. + * A model-to-view converter that attempts to merge nodes split by {@link module:list/list/converters~modelViewChangeType}. * * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event. diff --git a/packages/ckeditor5-list/src/indentcommand.js b/packages/ckeditor5-list/src/list/indentcommand.js similarity index 99% rename from packages/ckeditor5-list/src/indentcommand.js rename to packages/ckeditor5-list/src/list/indentcommand.js index 60eef5172a5..7c1b04ae6bd 100644 --- a/packages/ckeditor5-list/src/indentcommand.js +++ b/packages/ckeditor5-list/src/list/indentcommand.js @@ -4,7 +4,7 @@ */ /** - * @module list/indentcommand + * @module list/list/indentcommand */ import { Command } from 'ckeditor5/src/core'; diff --git a/packages/ckeditor5-list/src/listcommand.js b/packages/ckeditor5-list/src/list/listcommand.js similarity index 99% rename from packages/ckeditor5-list/src/listcommand.js rename to packages/ckeditor5-list/src/list/listcommand.js index 0144c1813e7..e89dffe0a7f 100644 --- a/packages/ckeditor5-list/src/listcommand.js +++ b/packages/ckeditor5-list/src/list/listcommand.js @@ -4,7 +4,7 @@ */ /** - * @module list/listcommand + * @module list/list/listcommand */ import { Command } from 'ckeditor5/src/core'; diff --git a/packages/ckeditor5-list/src/listediting.js b/packages/ckeditor5-list/src/list/listediting.js similarity index 99% rename from packages/ckeditor5-list/src/listediting.js rename to packages/ckeditor5-list/src/list/listediting.js index 7634aa2e64a..fdf021d8c17 100644 --- a/packages/ckeditor5-list/src/listediting.js +++ b/packages/ckeditor5-list/src/list/listediting.js @@ -4,7 +4,7 @@ */ /** - * @module list/listediting + * @module list/list/listediting */ import ListCommand from './listcommand'; diff --git a/packages/ckeditor5-list/src/listui.js b/packages/ckeditor5-list/src/list/listui.js similarity index 85% rename from packages/ckeditor5-list/src/listui.js rename to packages/ckeditor5-list/src/list/listui.js index 6c37f87fa6f..75c0d971d5b 100644 --- a/packages/ckeditor5-list/src/listui.js +++ b/packages/ckeditor5-list/src/list/listui.js @@ -4,13 +4,13 @@ */ /** - * @module list/listui + * @module list/list/listui */ import { createUIComponent } from './utils'; -import numberedListIcon from '../theme/icons/numberedlist.svg'; -import bulletedListIcon from '../theme/icons/bulletedlist.svg'; +import numberedListIcon from '../../theme/icons/numberedlist.svg'; +import bulletedListIcon from '../../theme/icons/bulletedlist.svg'; import { Plugin } from 'ckeditor5/src/core'; diff --git a/packages/ckeditor5-list/src/utils.js b/packages/ckeditor5-list/src/list/utils.js similarity index 99% rename from packages/ckeditor5-list/src/utils.js rename to packages/ckeditor5-list/src/list/utils.js index e4db7c1e668..af41540643e 100644 --- a/packages/ckeditor5-list/src/utils.js +++ b/packages/ckeditor5-list/src/list/utils.js @@ -4,7 +4,7 @@ */ /** - * @module list/utils + * @module list/list/utils */ import { TreeWalker, getFillerOffset } from 'ckeditor5/src/engine'; diff --git a/packages/ckeditor5-list/src/liststyle.js b/packages/ckeditor5-list/src/liststyle.js index 796332fe47c..43a46badc0f 100644 --- a/packages/ckeditor5-list/src/liststyle.js +++ b/packages/ckeditor5-list/src/liststyle.js @@ -8,14 +8,14 @@ */ import { Plugin } from 'ckeditor5/src/core'; -import ListStyleEditing from './liststyleediting'; -import ListStyleUI from './liststyleui'; +import ListStyleEditing from './liststyle/liststyleediting'; +import ListStyleUI from './liststyle/liststyleui'; /** * The list style feature. * - * This is a "glue" plugin that loads the {@link module:list/liststyleediting~ListStyleEditing list style editing feature} - * and the {@link module:list/liststyleui~ListStyleUI list style UI feature}. + * This is a "glue" plugin that loads the {@link module:list/liststyle/liststyleediting~ListStyleEditing list style editing feature} + * and the {@link module:list/liststyle/liststyleui~ListStyleUI list style UI feature}. * * @extends module:core/plugin~Plugin */ diff --git a/packages/ckeditor5-list/src/liststylecommand.js b/packages/ckeditor5-list/src/liststyle/liststylecommand.js similarity index 97% rename from packages/ckeditor5-list/src/liststylecommand.js rename to packages/ckeditor5-list/src/liststyle/liststylecommand.js index a2ce7c7e91b..ba6b95496b9 100644 --- a/packages/ckeditor5-list/src/liststylecommand.js +++ b/packages/ckeditor5-list/src/liststyle/liststylecommand.js @@ -4,11 +4,11 @@ */ /** - * @module list/liststylecommand + * @module list/liststyle/liststylecommand */ import { Command } from 'ckeditor5/src/core'; -import { getSiblingNodes } from './utils'; +import { getSiblingNodes } from '../list/utils'; /** * The list style command. It is used by the {@link module:list/liststyle~ListStyle list style feature}. diff --git a/packages/ckeditor5-list/src/liststyleediting.js b/packages/ckeditor5-list/src/liststyle/liststyleediting.js similarity index 99% rename from packages/ckeditor5-list/src/liststyleediting.js rename to packages/ckeditor5-list/src/liststyle/liststyleediting.js index 41c4f6ce1c1..7c162ba6a71 100644 --- a/packages/ckeditor5-list/src/liststyleediting.js +++ b/packages/ckeditor5-list/src/liststyle/liststyleediting.js @@ -4,13 +4,13 @@ */ /** - * @module list/liststyleediting + * @module list/liststyle/liststyleediting */ import { Plugin } from 'ckeditor5/src/core'; -import ListEditing from './listediting'; +import ListEditing from '../list/listediting'; import ListStyleCommand from './liststylecommand'; -import { getSiblingListItem, getSiblingNodes } from './utils'; +import { getSiblingListItem, getSiblingNodes } from '../list/utils'; const DEFAULT_LIST_TYPE = 'default'; diff --git a/packages/ckeditor5-list/src/liststyleui.js b/packages/ckeditor5-list/src/liststyle/liststyleui.js similarity index 86% rename from packages/ckeditor5-list/src/liststyleui.js rename to packages/ckeditor5-list/src/liststyle/liststyleui.js index 981dfcedadf..7cdface1260 100644 --- a/packages/ckeditor5-list/src/liststyleui.js +++ b/packages/ckeditor5-list/src/liststyle/liststyleui.js @@ -4,32 +4,32 @@ */ /** - * @module list/liststyleui + * @module list/liststyle/liststyleui */ import { Plugin } from 'ckeditor5/src/core'; import { ButtonView, SplitButtonView, createDropdown, addToolbarToDropdown } from 'ckeditor5/src/ui'; -import bulletedListIcon from '../theme/icons/bulletedlist.svg'; -import numberedListIcon from '../theme/icons/numberedlist.svg'; +import bulletedListIcon from '../../theme/icons/bulletedlist.svg'; +import numberedListIcon from '../../theme/icons/numberedlist.svg'; -import listStyleDiscIcon from '../theme/icons/liststyledisc.svg'; -import listStyleCircleIcon from '../theme/icons/liststylecircle.svg'; -import listStyleSquareIcon from '../theme/icons/liststylesquare.svg'; -import listStyleDecimalIcon from '../theme/icons/liststyledecimal.svg'; -import listStyleDecimalWithLeadingZeroIcon from '../theme/icons/liststyledecimalleadingzero.svg'; -import listStyleLowerRomanIcon from '../theme/icons/liststylelowerroman.svg'; -import listStyleUpperRomanIcon from '../theme/icons/liststyleupperroman.svg'; -import listStyleLowerLatinIcon from '../theme/icons/liststylelowerlatin.svg'; -import listStyleUpperLatinIcon from '../theme/icons/liststyleupperlatin.svg'; +import listStyleDiscIcon from '../../theme/icons/liststyledisc.svg'; +import listStyleCircleIcon from '../../theme/icons/liststylecircle.svg'; +import listStyleSquareIcon from '../../theme/icons/liststylesquare.svg'; +import listStyleDecimalIcon from '../../theme/icons/liststyledecimal.svg'; +import listStyleDecimalWithLeadingZeroIcon from '../../theme/icons/liststyledecimalleadingzero.svg'; +import listStyleLowerRomanIcon from '../../theme/icons/liststylelowerroman.svg'; +import listStyleUpperRomanIcon from '../../theme/icons/liststyleupperroman.svg'; +import listStyleLowerLatinIcon from '../../theme/icons/liststylelowerlatin.svg'; +import listStyleUpperLatinIcon from '../../theme/icons/liststyleupperlatin.svg'; -import '../theme/liststyles.css'; +import '../../theme/liststyles.css'; /** * The list style UI plugin. It introduces the extended `'bulletedList'` and `'numberedList'` toolbar * buttons that allow users to change styles of individual lists in the content. * - * **Note**: Buttons introduced by this plugin override implementations from the {@link module:list/listui~ListUI} + * **Note**: Buttons introduced by this plugin override implementations from the {@link module:list/list/listui~ListUI} * (because they share the same names). * * @extends module:core/plugin~Plugin @@ -174,7 +174,7 @@ function getSplitButtonCreator( { editor, parentCommandName, buttonLabel, button // // @param {Object} options // @param {module:core/editor/editor~Editor} options.editor -// @param {module:list/liststylecommand~ListStylesCommand} options.listStyleCommand The instance of the `ListStylesCommand` class. +// @param {module:list/liststyle/liststylecommand~ListStylesCommand} options.listStyleCommand The instance of the `ListStylesCommand` class. // @param {'bulletedList'|'numberedList'} options.parentCommandName The name of the higher-order command associated with a // particular list style (e.g. "bulletedList" is associated with "square" and "numberedList" is associated with "roman"). // @returns {Function} A function that can be passed straight into {@link module:ui/componentfactory~ComponentFactory#add}. diff --git a/packages/ckeditor5-list/src/todolist.js b/packages/ckeditor5-list/src/todolist.js index c3bdb439027..9fe25dd8371 100644 --- a/packages/ckeditor5-list/src/todolist.js +++ b/packages/ckeditor5-list/src/todolist.js @@ -7,16 +7,16 @@ * @module list/todolist */ -import TodoListEditing from './todolistediting'; -import TodoListUI from './todolistui'; +import TodoListEditing from './todolist/todolistediting'; +import TodoListUI from './todolist/todolistui'; import { Plugin } from 'ckeditor5/src/core'; import '../theme/todolist.css'; /** * The to-do list feature. * - * This is a "glue" plugin that loads the {@link module:list/todolistediting~TodoListEditing to-do list editing feature} - * and the {@link module:list/todolistui~TodoListUI to-do list UI feature}. + * This is a "glue" plugin that loads the {@link module:list/todolist/todolistediting~TodoListEditing to-do list editing feature} + * and the {@link module:list/todolist/todolistui~TodoListUI to-do list UI feature}. * * @extends module:core/plugin~Plugin */ diff --git a/packages/ckeditor5-list/src/checktodolistcommand.js b/packages/ckeditor5-list/src/todolist/checktodolistcommand.js similarity index 95% rename from packages/ckeditor5-list/src/checktodolistcommand.js rename to packages/ckeditor5-list/src/todolist/checktodolistcommand.js index 440eeacfcda..b8bd5fbd5c7 100644 --- a/packages/ckeditor5-list/src/checktodolistcommand.js +++ b/packages/ckeditor5-list/src/todolist/checktodolistcommand.js @@ -4,7 +4,7 @@ */ /** - * @module list/checktodolistcommand + * @module list/todolist/checktodolistcommand */ import { Command } from 'ckeditor5/src/core'; @@ -14,7 +14,7 @@ const attributeKey = 'todoListChecked'; /** * The check to-do command. * - * The command is registered by the {@link module:list/todolistediting~TodoListEditing} as + * The command is registered by the {@link module:list/todolist/todolistediting~TodoListEditing} as * the `checkTodoList` editor command and it is also available via aliased `todoListCheck` name. * * @extends module:core/command~Command diff --git a/packages/ckeditor5-list/src/todolistconverters.js b/packages/ckeditor5-list/src/todolist/todolistconverters.js similarity index 97% rename from packages/ckeditor5-list/src/todolistconverters.js rename to packages/ckeditor5-list/src/todolist/todolistconverters.js index 127990b2ef7..9323a68224a 100644 --- a/packages/ckeditor5-list/src/todolistconverters.js +++ b/packages/ckeditor5-list/src/todolist/todolistconverters.js @@ -4,14 +4,14 @@ */ /** - * @module list/todolistconverters + * @module list/todolist/todolistconverters */ /* global document */ import { createElement } from 'ckeditor5/src/utils'; -import { generateLiInUl, injectViewList, positionAfterUiElements, findNestedList } from './utils'; +import { generateLiInUl, injectViewList, positionAfterUiElements, findNestedList } from '../list/utils'; /** * A model-to-view converter for the `listItem` model element insertion. @@ -171,8 +171,8 @@ export function dataViewModelCheckmarkInsertion( evt, data, conversionApi ) { * {@link module:engine/view/uielement~UIElement checkbox UI element} is added at the beginning * of the list item element (or vice versa). * - * This converter is preceded by {@link module:list/converters~modelViewChangeType} and followed by - * {@link module:list/converters~modelViewMergeAfterChangeType} to handle splitting and merging surrounding lists of the same type. + * This converter is preceded by {@link module:list/list/converters~modelViewChangeType} and followed by + * {@link module:list/list/converters~modelViewMergeAfterChangeType} to handle splitting and merging surrounding lists of the same type. * * It is used by {@link module:engine/controller/editingcontroller~EditingController}. * diff --git a/packages/ckeditor5-list/src/todolistediting.js b/packages/ckeditor5-list/src/todolist/todolistediting.js similarity index 97% rename from packages/ckeditor5-list/src/todolistediting.js rename to packages/ckeditor5-list/src/todolist/todolistediting.js index 2b93fbb82a8..929cf9460b6 100644 --- a/packages/ckeditor5-list/src/todolistediting.js +++ b/packages/ckeditor5-list/src/todolist/todolistediting.js @@ -4,7 +4,7 @@ */ /** - * @module list/todolistediting + * @module list/todolist/todolistediting */ import { Plugin } from 'ckeditor5/src/core'; @@ -14,8 +14,8 @@ import { getLocalizedArrowKeyCodeDirection } from 'ckeditor5/src/utils'; -import ListCommand from './listcommand'; -import ListEditing from './listediting'; +import ListCommand from '../list/listcommand'; +import ListEditing from '../list/listediting'; import CheckTodoListCommand from './checktodolistcommand'; import { dataModelViewInsertion, @@ -31,7 +31,7 @@ const ITEM_TOGGLE_KEYSTROKE = parseKeystroke( 'Ctrl+Enter' ); /** * The engine of the to-do list feature. It handles creating, editing and removing to-do lists and their items. * - * It registers the entire functionality of the {@link module:list/listediting~ListEditing list editing plugin} and extends + * It registers the entire functionality of the {@link module:list/list/listediting~ListEditing list editing plugin} and extends * it with the commands: * * - `'todoList'`, diff --git a/packages/ckeditor5-list/src/todolistui.js b/packages/ckeditor5-list/src/todolist/todolistui.js similarity index 83% rename from packages/ckeditor5-list/src/todolistui.js rename to packages/ckeditor5-list/src/todolist/todolistui.js index 072a79665d6..2ea28a3056a 100644 --- a/packages/ckeditor5-list/src/todolistui.js +++ b/packages/ckeditor5-list/src/todolist/todolistui.js @@ -4,11 +4,11 @@ */ /** - * @module list/todolistui + * @module list/todolist/todolistui */ -import { createUIComponent } from './utils'; -import todoListIcon from '../theme/icons/todolist.svg'; +import { createUIComponent } from '../list/utils'; +import todoListIcon from '../../theme/icons/todolist.svg'; import { Plugin } from 'ckeditor5/src/core'; /** diff --git a/packages/ckeditor5-list/tests/list.js b/packages/ckeditor5-list/tests/list.js index f83e809cb80..f13a4960957 100644 --- a/packages/ckeditor5-list/tests/list.js +++ b/packages/ckeditor5-list/tests/list.js @@ -4,8 +4,8 @@ */ import List from '../src/list'; -import ListEditing from '../src/listediting'; -import ListUI from '../src/listui'; +import ListEditing from '../src/list/listediting'; +import ListUI from '../src/list/listui'; describe( 'List', () => { it( 'should be named', () => { diff --git a/packages/ckeditor5-list/tests/indentcommand.js b/packages/ckeditor5-list/tests/list/indentcommand.js similarity index 99% rename from packages/ckeditor5-list/tests/indentcommand.js rename to packages/ckeditor5-list/tests/list/indentcommand.js index 2723e4dbbb4..85b73d4630c 100644 --- a/packages/ckeditor5-list/tests/indentcommand.js +++ b/packages/ckeditor5-list/tests/list/indentcommand.js @@ -5,7 +5,7 @@ import Editor from '@ckeditor/ckeditor5-core/src/editor/editor'; import Model from '@ckeditor/ckeditor5-engine/src/model/model'; -import IndentCommand from '../src/indentcommand'; +import IndentCommand from '../../src/list/indentcommand'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; describe( 'IndentCommand', () => { diff --git a/packages/ckeditor5-list/tests/listcommand.js b/packages/ckeditor5-list/tests/list/listcommand.js similarity index 99% rename from packages/ckeditor5-list/tests/listcommand.js rename to packages/ckeditor5-list/tests/list/listcommand.js index d54631b69df..066e63526cc 100644 --- a/packages/ckeditor5-list/tests/listcommand.js +++ b/packages/ckeditor5-list/tests/list/listcommand.js @@ -5,7 +5,7 @@ import Editor from '@ckeditor/ckeditor5-core/src/editor/editor'; import Model from '@ckeditor/ckeditor5-engine/src/model/model'; -import ListCommand from '../src/listcommand'; +import ListCommand from '../../src/list/listcommand'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; describe( 'ListCommand', () => { diff --git a/packages/ckeditor5-list/tests/listediting.js b/packages/ckeditor5-list/tests/list/listediting.js similarity index 99% rename from packages/ckeditor5-list/tests/listediting.js rename to packages/ckeditor5-list/tests/list/listediting.js index ae945fe509a..e577d02e745 100644 --- a/packages/ckeditor5-list/tests/listediting.js +++ b/packages/ckeditor5-list/tests/list/listediting.js @@ -3,9 +3,9 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import ListEditing from '../src/listediting'; -import ListCommand from '../src/listcommand'; -import IndentCommand from '../src/indentcommand'; +import ListEditing from '../../src/list/listediting'; +import ListCommand from '../../src/list/listcommand'; +import IndentCommand from '../../src/list/indentcommand'; import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range'; diff --git a/packages/ckeditor5-list/tests/listui.js b/packages/ckeditor5-list/tests/list/listui.js similarity index 96% rename from packages/ckeditor5-list/tests/listui.js rename to packages/ckeditor5-list/tests/list/listui.js index 6a3cee303d6..428d1baed47 100644 --- a/packages/ckeditor5-list/tests/listui.js +++ b/packages/ckeditor5-list/tests/list/listui.js @@ -5,8 +5,8 @@ /* globals document */ -import ListEditing from '../src/listediting'; -import ListUI from '../src/listui'; +import ListEditing from '../../src/list/listediting'; +import ListUI from '../../src/list/listui'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; diff --git a/packages/ckeditor5-list/tests/utils.js b/packages/ckeditor5-list/tests/list/utils.js similarity index 99% rename from packages/ckeditor5-list/tests/utils.js rename to packages/ckeditor5-list/tests/list/utils.js index 8184aaf6d78..2e26b47096d 100644 --- a/packages/ckeditor5-list/tests/utils.js +++ b/packages/ckeditor5-list/tests/list/utils.js @@ -8,10 +8,10 @@ import ViewDowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwrit import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; -import ListEditing from '../src/listediting'; -import ListStyleEditing from '../src/liststyleediting'; +import ListEditing from '../../src/list/listediting'; +import ListStyleEditing from '../../src/liststyle/liststyleediting'; -import { createViewListItemElement, getSiblingListItem, getSiblingNodes } from '../src/utils'; +import { createViewListItemElement, getSiblingListItem, getSiblingNodes } from '../../src/list/utils'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; describe( 'utils', () => { diff --git a/packages/ckeditor5-list/tests/liststyle.js b/packages/ckeditor5-list/tests/liststyle.js index deec80f0262..8284924baa2 100644 --- a/packages/ckeditor5-list/tests/liststyle.js +++ b/packages/ckeditor5-list/tests/liststyle.js @@ -4,8 +4,8 @@ */ import ListStyle from '../src/liststyle'; -import ListStyleEditing from '../src/liststyleediting'; -import ListStyleUI from '../src/liststyleui'; +import ListStyleEditing from '../src/liststyle/liststyleediting'; +import ListStyleUI from '../src/liststyle/liststyleui'; describe( 'ListStyle', () => { it( 'should be named', () => { diff --git a/packages/ckeditor5-list/tests/liststylecommand.js b/packages/ckeditor5-list/tests/liststyle/liststylecommand.js similarity index 99% rename from packages/ckeditor5-list/tests/liststylecommand.js rename to packages/ckeditor5-list/tests/liststyle/liststylecommand.js index cf2f5a4c46d..df6a2e0a29c 100644 --- a/packages/ckeditor5-list/tests/liststylecommand.js +++ b/packages/ckeditor5-list/tests/liststyle/liststylecommand.js @@ -6,7 +6,7 @@ import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import ListStyleEditing from '../src/liststyleediting'; +import ListStyleEditing from '../../src/liststyle/liststyleediting'; describe( 'ListStyleCommand', () => { let editor, model, bulletedListCommand, numberedListCommand, listStyleCommand; diff --git a/packages/ckeditor5-list/tests/liststyleediting.js b/packages/ckeditor5-list/tests/liststyle/liststyleediting.js similarity index 99% rename from packages/ckeditor5-list/tests/liststyleediting.js rename to packages/ckeditor5-list/tests/liststyle/liststyleediting.js index cf34df76742..bf6ea662c35 100644 --- a/packages/ckeditor5-list/tests/liststyleediting.js +++ b/packages/ckeditor5-list/tests/liststyle/liststyleediting.js @@ -14,9 +14,9 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; -import ListStyleEditing from '../src/liststyleediting'; -import TodoListEditing from '../src/todolistediting'; -import ListStyleCommand from '../src/liststylecommand'; +import ListStyleEditing from '../../src/liststyle/liststyleediting'; +import TodoListEditing from '../../src/todolist/todolistediting'; +import ListStyleCommand from '../../src/liststyle/liststylecommand'; import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor'; describe( 'ListStyleEditing', () => { diff --git a/packages/ckeditor5-list/tests/liststyleui.js b/packages/ckeditor5-list/tests/liststyle/liststyleui.js similarity index 94% rename from packages/ckeditor5-list/tests/liststyleui.js rename to packages/ckeditor5-list/tests/liststyle/liststyleui.js index e4cdf01698e..3db3b79bafd 100644 --- a/packages/ckeditor5-list/tests/liststyleui.js +++ b/packages/ckeditor5-list/tests/liststyle/liststyleui.js @@ -10,23 +10,23 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; -import ListStyle from '../src/liststyle'; -import ListStyleUI from '../src/liststyleui'; +import ListStyle from '../../src/liststyle'; +import ListStyleUI from '../../src/liststyle/liststyleui'; import DropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/dropdownview'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; -import bulletedListIcon from '../theme/icons/bulletedlist.svg'; -import numberedListIcon from '../theme/icons/numberedlist.svg'; - -import listStyleDiscIcon from '../theme/icons/liststyledisc.svg'; -import listStyleCircleIcon from '../theme/icons/liststylecircle.svg'; -import listStyleSquareIcon from '../theme/icons/liststylesquare.svg'; -import listStyleDecimalIcon from '../theme/icons/liststyledecimal.svg'; -import listStyleDecimalWithLeadingZeroIcon from '../theme/icons/liststyledecimalleadingzero.svg'; -import listStyleLowerRomanIcon from '../theme/icons/liststylelowerroman.svg'; -import listStyleUpperRomanIcon from '../theme/icons/liststyleupperroman.svg'; -import listStyleLowerLatinIcon from '../theme/icons/liststylelowerlatin.svg'; -import listStyleUpperLatinIcon from '../theme/icons/liststyleupperlatin.svg'; +import bulletedListIcon from '../../theme/icons/bulletedlist.svg'; +import numberedListIcon from '../../theme/icons/numberedlist.svg'; + +import listStyleDiscIcon from '../../theme/icons/liststyledisc.svg'; +import listStyleCircleIcon from '../../theme/icons/liststylecircle.svg'; +import listStyleSquareIcon from '../../theme/icons/liststylesquare.svg'; +import listStyleDecimalIcon from '../../theme/icons/liststyledecimal.svg'; +import listStyleDecimalWithLeadingZeroIcon from '../../theme/icons/liststyledecimalleadingzero.svg'; +import listStyleLowerRomanIcon from '../../theme/icons/liststylelowerroman.svg'; +import listStyleUpperRomanIcon from '../../theme/icons/liststyleupperroman.svg'; +import listStyleLowerLatinIcon from '../../theme/icons/liststylelowerlatin.svg'; +import listStyleUpperLatinIcon from '../../theme/icons/liststyleupperlatin.svg'; describe( 'ListStyleUI', () => { let editorElement, editor, model, listStyleCommand; diff --git a/packages/ckeditor5-list/tests/todolist.js b/packages/ckeditor5-list/tests/todolist.js index 1532f9019da..a1636954c09 100644 --- a/packages/ckeditor5-list/tests/todolist.js +++ b/packages/ckeditor5-list/tests/todolist.js @@ -4,8 +4,8 @@ */ import TodoList from '../src/todolist'; -import TodoListEditing from '../src/todolistediting'; -import TodoListUI from '../src/todolistui'; +import TodoListEditing from '../src/todolist/todolistediting'; +import TodoListUI from '../src/todolist/todolistui'; describe( 'TodoList', () => { it( 'should be named', () => { diff --git a/packages/ckeditor5-list/tests/checktodolistcommand.js b/packages/ckeditor5-list/tests/todolist/checktodolistcommand.js similarity index 98% rename from packages/ckeditor5-list/tests/checktodolistcommand.js rename to packages/ckeditor5-list/tests/todolist/checktodolistcommand.js index 1e1b68c8d8d..2a5be759ab0 100644 --- a/packages/ckeditor5-list/tests/checktodolistcommand.js +++ b/packages/ckeditor5-list/tests/todolist/checktodolistcommand.js @@ -3,8 +3,8 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import TodoListEditing from '../src/todolistediting'; -import CheckTodoListCommand from '../src/checktodolistcommand'; +import TodoListEditing from '../../src/todolist/todolistediting'; +import CheckTodoListCommand from '../../src/todolist/checktodolistcommand'; import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; diff --git a/packages/ckeditor5-list/tests/todolistediting.js b/packages/ckeditor5-list/tests/todolist/todolistediting.js similarity index 99% rename from packages/ckeditor5-list/tests/todolistediting.js rename to packages/ckeditor5-list/tests/todolist/todolistediting.js index ecdd9f9ed61..e9840cab120 100644 --- a/packages/ckeditor5-list/tests/todolistediting.js +++ b/packages/ckeditor5-list/tests/todolist/todolistediting.js @@ -3,13 +3,13 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import TodoListEditing from '../src/todolistediting'; -import ListEditing from '../src/listediting'; +import TodoListEditing from '../../src/todolist/todolistediting'; +import ListEditing from '../../src/list/listediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting'; import Typing from '@ckeditor/ckeditor5-typing/src/typing'; -import ListCommand from '../src/listcommand'; -import CheckTodoListCommand from '../src/checktodolistcommand'; +import ListCommand from '../../src/list/listcommand'; +import CheckTodoListCommand from '../../src/todolist/checktodolistcommand'; import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element'; import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting'; diff --git a/packages/ckeditor5-list/tests/todolistui.js b/packages/ckeditor5-list/tests/todolist/todolistui.js similarity index 93% rename from packages/ckeditor5-list/tests/todolistui.js rename to packages/ckeditor5-list/tests/todolist/todolistui.js index 895080b1da7..89cdc441a35 100644 --- a/packages/ckeditor5-list/tests/todolistui.js +++ b/packages/ckeditor5-list/tests/todolist/todolistui.js @@ -5,8 +5,8 @@ /* globals document */ -import TodoListEditing from '../src/todolistediting'; -import TodoListUI from '../src/todolistui'; +import TodoListEditing from '../../src/todolist/todolistediting'; +import TodoListUI from '../../src/todolist/todolistui'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; diff --git a/packages/ckeditor5-table/tests/table-integration.js b/packages/ckeditor5-table/tests/table-integration.js index 3e21b059753..347317dec32 100644 --- a/packages/ckeditor5-table/tests/table-integration.js +++ b/packages/ckeditor5-table/tests/table-integration.js @@ -8,7 +8,7 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting'; import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; diff --git a/packages/ckeditor5-table/tests/tableclipboard-paste.js b/packages/ckeditor5-table/tests/tableclipboard-paste.js index 9783f059eba..bacf27d8d3b 100644 --- a/packages/ckeditor5-table/tests/tableclipboard-paste.js +++ b/packages/ckeditor5-table/tests/tableclipboard-paste.js @@ -10,7 +10,7 @@ import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteedi import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import HorizontalLineEditing from '@ckeditor/ckeditor5-horizontal-line/src/horizontallineediting'; import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import ImageBlockEditing from '@ckeditor/ckeditor5-image/src/image/imageblockediting'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Input from '@ckeditor/ckeditor5-typing/src/input'; diff --git a/packages/ckeditor5-word-count/tests/utils.js b/packages/ckeditor5-word-count/tests/utils.js index cddf2c7cd76..7a3e6ba5fe6 100644 --- a/packages/ckeditor5-word-count/tests/utils.js +++ b/packages/ckeditor5-word-count/tests/utils.js @@ -13,7 +13,7 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting'; import Enter from '@ckeditor/ckeditor5-enter/src/enter'; import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter'; diff --git a/packages/ckeditor5-word-count/tests/wordcount.js b/packages/ckeditor5-word-count/tests/wordcount.js index 15683cdd138..a77da5f6b2a 100644 --- a/packages/ckeditor5-word-count/tests/wordcount.js +++ b/packages/ckeditor5-word-count/tests/wordcount.js @@ -16,7 +16,7 @@ import Position from '@ckeditor/ckeditor5-engine/src/model/position'; import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter'; import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting'; import env from '@ckeditor/ckeditor5-utils/src/env'; -import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'; +import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting'; import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting'; import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting'; import ImageBlockEditing from '@ckeditor/ckeditor5-image/src/image/imageblockediting'; From 999cfaa5f48b945225f5e1c69a37067dbe87bc4e Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Mon, 15 Nov 2021 12:05:01 +0100 Subject: [PATCH 102/140] Updated links in docs. --- docs/builds/guides/migration/migration-to-26.md | 2 +- packages/ckeditor5-list/docs/features/lists.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/builds/guides/migration/migration-to-26.md b/docs/builds/guides/migration/migration-to-26.md index 917b35c82d3..d2a4cc50de0 100644 --- a/docs/builds/guides/migration/migration-to-26.md +++ b/docs/builds/guides/migration/migration-to-26.md @@ -216,7 +216,7 @@ Command name changes (before → after): * `forwardDelete` → `deleteForward` * `todoListCheck` → `checkTodoList` -The `TodoListCheckCommand` module was moved to {@link module:list/checktodolistcommand~CheckTodoListCommand `CheckTodoListCommand`}. +The `TodoListCheckCommand` module was moved to {@link module:list/todolist/checktodolistcommand~CheckTodoListCommand `CheckTodoListCommand`}. The `ImageInsertCommand` module was moved to {@link module:image/image/insertimagecommand~InsertImageCommand `InsertImageCommand`}. diff --git a/packages/ckeditor5-list/docs/features/lists.md b/packages/ckeditor5-list/docs/features/lists.md index c4499171ac4..cd9d1499f5d 100644 --- a/packages/ckeditor5-list/docs/features/lists.md +++ b/packages/ckeditor5-list/docs/features/lists.md @@ -79,7 +79,7 @@ ClassicEditor - The {@link module:list/liststyle~ListStyle} feature overrides UI button implementations from the {@link module:list/listui~ListUI}. + The {@link module:list/liststyle~ListStyle} feature overrides UI button implementations from the {@link module:list/list/listui~ListUI}. ## List indentation @@ -90,16 +90,16 @@ Refer to the {@link features/indent Indenting lists} section of the Block indent The {@link module:list/list~List} plugin registers: -* The {@link module:list/listcommand~ListCommand `'numberedList'`} command. -* The {@link module:list/listcommand~ListCommand `'bulletedList'`} command. -* The {@link module:list/indentcommand~IndentCommand `'indentList'`} command. -* The {@link module:list/indentcommand~IndentCommand `'outdentList'`} command. +* The {@link module:list/list/listcommand~ListCommand `'numberedList'`} command. +* The {@link module:list/list/listcommand~ListCommand `'bulletedList'`} command. +* The {@link module:list/list/indentcommand~IndentCommand `'indentList'`} command. +* The {@link module:list/list/indentcommand~IndentCommand `'outdentList'`} command. * The `'numberedList'` UI button. * The `'bulletedList'` UI button. The {@link module:list/liststyle~ListStyle} plugin registers: -* The {@link module:list/liststylecommand~ListStyleCommand `'listStyle'`} command that accepts a `type` of the list style to set. +* The {@link module:list/liststyle/liststylecommand~ListStyleCommand `'listStyle'`} command that accepts a `type` of the list style to set. ```js editor.execute( 'listStyle', { type: 'decimal' } ); ``` From acfb3e0b543add02050340116b2c375ff891ac9e Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Thu, 20 Jan 2022 17:13:58 +0100 Subject: [PATCH 103/140] Updated copyright header. --- packages/ckeditor5-engine/tests/manual/element-reconversion.js | 2 +- .../src/converters/table-headings-refresh-handler.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/tests/manual/element-reconversion.js b/packages/ckeditor5-engine/tests/manual/element-reconversion.js index ccdffb131d4..b7c1f61cccc 100644 --- a/packages/ckeditor5-engine/tests/manual/element-reconversion.js +++ b/packages/ckeditor5-engine/tests/manual/element-reconversion.js @@ -1,5 +1,5 @@ /** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ diff --git a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js index c397dfdb442..9b2aa448eac 100644 --- a/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js +++ b/packages/ckeditor5-table/src/converters/table-headings-refresh-handler.js @@ -1,5 +1,5 @@ /** - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ From 88f5e0864ff113f4b72ae89bf1562ba41aef9859 Mon Sep 17 00:00:00 2001 From: dawidurbanski Date: Wed, 19 Jan 2022 10:11:15 +0100 Subject: [PATCH 104/140] Add decoupled editor to engine package dev dependencies. --- package.json | 4 +- .../framework/mini-inspector-bold.html | 5 + .../framework/mini-inspector-bold.js | 20 + .../mini-inspector-heading-interactive.html | 37 + .../mini-inspector-heading-interactive.js | 61 ++ .../framework/mini-inspector-heading.html | 5 + .../framework/mini-inspector-heading.js | 33 + .../framework/mini-inspector-paragraph.html | 5 + .../framework/mini-inspector-paragraph.js | 20 + .../framework/mini-inspector-structure.html | 5 + .../framework/mini-inspector-structure.js | 40 + .../_snippets/framework/mini-inspector.html | 11 + .../_snippets/framework/mini-inspector.js | 12 + .../docs/assets/img/downcast-basic.png | Bin 0 -> 108101 bytes .../docs/assets/img/downcast-pipelines.png | Bin 0 -> 175863 bytes .../docs/assets/img/upcast-basic.png | Bin 0 -> 249673 bytes .../docs/assets/img/upcast-pipeline.png | Bin 0 -> 525218 bytes .../guides/deep-dive/conversion-downcast.md | 197 +++++ .../guides/deep-dive/conversion-intro.md | 36 + .../guides/deep-dive/conversion-upcast.md | 147 ++++ .../guides/mini-inspector/miniinspector.js | 737 ++++++++++++++++++ .../miniinspector.js.LICENSE.txt | 51 ++ packages/ckeditor5-engine/package.json | 1 + .../tests/manual/element-reconversion.js | 3 +- .../mini-inspector-heading-interactive.html | 28 + .../mini-inspector-heading-interactive.js | 92 +++ .../mini-inspector-heading-interactive.md | 1 + 27 files changed, 1548 insertions(+), 3 deletions(-) create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.js create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.js create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.html create mode 100644 packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js create mode 100644 packages/ckeditor5-engine/docs/assets/img/downcast-basic.png create mode 100644 packages/ckeditor5-engine/docs/assets/img/downcast-pipelines.png create mode 100644 packages/ckeditor5-engine/docs/assets/img/upcast-basic.png create mode 100644 packages/ckeditor5-engine/docs/assets/img/upcast-pipeline.png create mode 100644 packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-downcast.md create mode 100644 packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-intro.md create mode 100644 packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-upcast.md create mode 100644 packages/ckeditor5-engine/docs/framework/guides/mini-inspector/miniinspector.js create mode 100644 packages/ckeditor5-engine/docs/framework/guides/mini-inspector/miniinspector.js.LICENSE.txt create mode 100644 packages/ckeditor5-engine/tests/manual/mini-inspector-heading-interactive.html create mode 100644 packages/ckeditor5-engine/tests/manual/mini-inspector-heading-interactive.js create mode 100644 packages/ckeditor5-engine/tests/manual/mini-inspector-heading-interactive.md diff --git a/package.json b/package.json index 2c1acc2e19b..bc43a331e8b 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,7 @@ "release:find-plugins": "node ./scripts/docs/find-new-plugins.js", "docs:serve": "http-server ./build/docs/", "docs:verify": "node ./scripts/web-crawler/index.js --docs", + "docs:mini-inspector": "", "translations:collect": "node ./scripts/translations.js collect", "translations:download": "node ./scripts/translations.js download", "translations:upload": "node ./scripts/translations.js upload", @@ -194,7 +195,8 @@ "packages/*/build/**", "packages/*/src/lib/**", "coverage/**", - "external/**" + "external/**", + "packages/ckeditor5-engine/docs/framework/guides/mini-inspector" ], "workspaces": { "packages": [ diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.html new file mode 100644 index 00000000000..397850711ef --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.html @@ -0,0 +1,5 @@ +
        +

        Text in bold

        +
        + +
        diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.js new file mode 100644 index 00000000000..ef5e127cf9e --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-bold.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals DecoupledEditor, MiniCKEditorInspector, console, window, document */ + +DecoupledEditor + .create( document.querySelector( '#mini-inspector-bold' ) ) + .then( editor => { + window.editor = editor; + + MiniCKEditorInspector.attach( + editor, + document.querySelector( '#mini-inspector-bold-container' ) + ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.html new file mode 100644 index 00000000000..fbc41a00122 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.html @@ -0,0 +1,37 @@ +
        +

        +
        + + + +
        + + diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js new file mode 100644 index 00000000000..17ff5ab99f7 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js @@ -0,0 +1,61 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals DecoupledEditor, MiniCKEditorInspector, Essentials, console, window, document */ + +function CustomHeading( editor ) { + editor.model.schema.register( 'heading', { + allowAttributes: [ 'level' ], + inheritAllFrom: '$block' + } ); + + editor.conversion.for( 'upcast' ).elementToElement( { + view: 'h1', + model: ( viewElement, { writer } ) => { + return writer.createElement( 'heading', { level: '1' } ); + } + } ); + + editor.conversion.for( 'downcast' ).elementToElement( { + model: { + name: 'heading', + attributes: [ 'level' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( + 'h' + modelElement.getAttribute( 'level' ) + ); + } + } ); + + const dropdown = document.getElementById( + 'mini-inspector-heading-interactive-dropdown' + ); + + dropdown.addEventListener( 'change', event => { + editor.model.change( writer => { + writer.setAttribute( + 'level', + event.target.value, + editor.model.document.getRoot().getChild( 0 ) + ); + } ); + } ); +} + +DecoupledEditor.create( document.querySelector( '#mini-inspector-heading-interactive' ), { + plugins: [ Essentials, CustomHeading ] +} ) + .then( editor => { + window.editor = editor; + + MiniCKEditorInspector.attach( + editor, + document.querySelector( '#mini-inspector-heading-interactive-container' ) + ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.html new file mode 100644 index 00000000000..7fc4bd8a457 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.html @@ -0,0 +1,5 @@ +
        +

        +
        + +
        diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js new file mode 100644 index 00000000000..215297dac16 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js @@ -0,0 +1,33 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals DecoupledEditor, MiniCKEditorInspector, Essentials, console, window, document */ + +function CustomHeading( editor ) { + editor.model.schema.register( 'heading', { + allowAttributes: [ 'level' ], + inheritAllFrom: '$block' + } ); + + editor.conversion.elementToElement( { + model: 'heading', + view: 'h1' + } ); +} + +DecoupledEditor.create( document.querySelector( '#mini-inspector-heading' ), { + plugins: [ Essentials, CustomHeading ] +} ) + .then( editor => { + window.editor = editor; + + MiniCKEditorInspector.attach( + editor, + document.querySelector( '#mini-inspector-heading-container' ) + ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.html new file mode 100644 index 00000000000..77ea652795b --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.html @@ -0,0 +1,5 @@ +
        +

        +
        + +
        diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.js new file mode 100644 index 00000000000..27c700153a7 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-paragraph.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals DecoupledEditor, MiniCKEditorInspector, console, window, document */ + +DecoupledEditor + .create( document.querySelector( '#mini-inspector-paragraph' ) ) + .then( editor => { + window.editor = editor; + + MiniCKEditorInspector.attach( + editor, + document.querySelector( '#mini-inspector-paragraph-container' ) + ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.html new file mode 100644 index 00000000000..d9f04b47caa --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.html @@ -0,0 +1,5 @@ +
        +

        +
        + +
        diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js new file mode 100644 index 00000000000..eeb26e8779b --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js @@ -0,0 +1,40 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals DecoupledEditor, MiniCKEditorInspector, Essentials, console, window, document */ + +function Structure( editor ) { + editor.model.schema.register( 'myElement', { + inheritAllFrom: '$block' + } ); + + editor.conversion.elementToElement( { + model: 'myElement', + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', { class: 'wrapper' }, [ + writer.createContainerElement( 'p' ) + ] ); + } + } ); +} + +DecoupledEditor.create( document.querySelector( '#mini-inspector-structure' ), { + plugins: [ Essentials, Structure ] +} ) + .then( editor => { + window.editor = editor; + + editor.model.change( writer => { + writer.insertElement( 'myElement', 0 ); + } ); + + MiniCKEditorInspector.attach( + editor, + document.querySelector( '#mini-inspector-structure-container' ) + ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.html b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.html new file mode 100644 index 00000000000..37d346859d1 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.html @@ -0,0 +1,11 @@ + + + diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js new file mode 100644 index 00000000000..647014bb253 --- /dev/null +++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals window */ + +import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document/src/ckeditor'; +import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'; + +window.DecoupledEditor = DecoupledEditor; +window.Essentials = Essentials; diff --git a/packages/ckeditor5-engine/docs/assets/img/downcast-basic.png b/packages/ckeditor5-engine/docs/assets/img/downcast-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..b9dadfa9f6d214fa70733bdab6c47a28d59f4c11 GIT binary patch literal 108101 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

        qvTYjbAdJu7cblqil4qh0qj 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

        #DZ;TwXwfNxE;1JT_ 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 Z>_a5F<6`kx@CnX+d#R$OT%l+l`d=n4gbV-x literal 0 HcmV?d00001 diff --git a/packages/ckeditor5-engine/docs/assets/img/upcast-basic.png b/packages/ckeditor5-engine/docs/assets/img/upcast-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..78c8f824a74560ccee3e34905ea58a1cec7f81e9 GIT binary patch literal 249673 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�YlqB8-dMG?)>lMU(GpI>8>PNV9 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 Dj9%1p3edFUo~HwaCaW?BQmYWo&DA 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 z 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

        %CN@QDv_rVU|1m&8-G4Ia1PT_ib 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 z�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�y_9faj^GJ}cwf9m49Z@`!*}|b!Ml88Nwx{JWLnWc23yieo8xYz? zk~Xo6&C!@aROy$U!f~TbNru@iL*{ukS!=k}XHeL6W0bhlJ{D1%ae*a-wnA6lqIfn; zyoAq4kė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

      1. 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�v+eZ}-^|L6vOx6e`ioSF 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 literal 0 HcmV?d00001 diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-downcast.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-downcast.md new file mode 100644 index 00000000000..754fd9486a9 --- /dev/null +++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-downcast.md @@ -0,0 +1,197 @@ +--- +category: framework-deep-dive-conversion +menu-title: Model to view - downcast +order: 1 +--- + +# Model to view - downcast + +## Introduction + +The process of converting the **model** to the **view** is called a **downcast**. + +{@img assets/img/downcast-basic.png 760 Basic downcast conversion diagram.} + +The downcast process happens every time a model node or attribute needs to be converted into a view node or attribute. + +The editor engine runs the conversion process and uses converters registered by plugins. + +{@snippet framework/mini-inspector} + +## Registering a converter + +In order to tell the engine how to convert a specific model element into a view element, you need to register a **downcast converter** by using the `editor.conversion.for( 'downcast' )` method: + +```js +editor.conversion + .for( 'downcast' ) + .elementToElement( { + model: 'paragraph', + view: 'p' + } ); +``` + +The above converter will handle the conversion of every `` model element to a `

        ` view element. + +{@snippet framework/mini-inspector-paragraph} + + + This is just an example. Paragraph support is provided by the {@link api/paragraph paragraph plugin} so you don't have to write your own `` element to `

        ` element conversion. + + + + You just learned about the `elementToElement()` **downcast** conversion helper method! More helpers are documented in the following chapters. + + +## Downcast pipelines + +CKEditor 5 engine uses two different views: **data view** and **editing view**. + +**The data view** is used when generating editor output. This process is controlled by the data pipeline. + +**The editing view**, on the other hand, is what you see when you open the editor, which is controlled by the editing pipeline. + +{@img assets/img/downcast-pipelines.png 760 Downcast conversion pipelines diagram.} + +The previous code example registers a converter for both pipelines at once. It means that `` model element will be converted to a `

        ` view element in both **data view** and **editing view**. + +Sometimes you may want to alter converter logic for a specific pipeline. For example, in editing view you want to add some additional class to the view element. + +```js +// dataDowncast for data pipeline +editor.conversion + .for( 'dataDowncast' ) + .elementToElement( { + model: 'paragraph', + view: 'p' + } ); + +// editingDowncast for editing pipeline +editor.conversion + .for( 'editingDowncast' ) + .elementToElement( { + model: 'paragraph', + view: { + name: 'p', + classes: 'paragraph-in-editing-view' + } + } ); +``` + +{@snippet framework/mini-inspector-paragraph} + +## Converting text attributes + +As you may know from the chapter about the model, an **attribute** can be applied to a model text node. + +Such text nodes attributes can be converted into view elements. + +In order to do so, you can register a converter by using attributeToElement() conversion helper: + +```js +editor.conversion + .for( 'downcast' ) + .attributeToElement( { + model: 'bold', + view: 'strong' + } ); +``` + +The above converter will handle the conversion of every `bold` model text node attribute to a `` view element. + +{@snippet framework/mini-inspector-bold} + + + This is just an example. Bold support is provided by the {@link features/basic-styles basic styles} plugin so you don't have to write your own bold attribute to strong element conversion. + + +## Converting element to element + +Similar to the basic example, you can convert `` model element into `

        ` view element with `elementToElement()` conversion helper. + +```js +editor.conversion + .for( 'downcast' ) + .elementToElement( { + model: 'heading', + view: 'h1' + } ); +``` + +Which is equivalent to: + +```js +editor.conversion + .for( 'downcast' ) + .elementToElement( { + model: 'heading', + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( + 'h1' + ); + } + } ); +``` + +You learned that the `view` property can be a simple string or an object. The example above shows it is also possible to define a custom callback function to return created element instead. + +{@snippet framework/mini-inspector-heading} + +The `` element makes the most sense if you can set the heading level. + +From the previous chapter you learned that you can apply attributes to text nodes. It is also possible to add attributes to elements. + +```js +editor.conversion + .for( 'downcast' ) + .elementToElement( { + model: { + name: 'heading', + attributes: [ 'level' ] + }, + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( + 'h' + modelElement.getAttribute( 'level' ) + ); + } + } ); +``` + +From now on, every time level attribute updates, the whole `` element will be converted to the `` element (for example `

        `, `

        `, etc). + +You can check this in action by using the example below: + +{@snippet framework/mini-inspector-heading-interactive} + + + This is just an example. Heading support is provided by the heading feature {@link features/headings headings feature} so you don't have to write your own `` to `

        ` element conversion. + + +## Converting element to structure + +Sometimes you may want to convert a **single model** element into a more complex view structure consisting of a **single view element with children**. + +You can use `elementToStructure()` conversion helper for this purpose: + +```js +editor.conversion + .for( 'downcast' ) + .elementToStructure( { + model: 'myElement', + view: ( modelElement, { writer } ) => { + return writer.createContainerElement( 'div', { class: 'wrapper' }, [ + writer.createContainterElement( 'p' ) + ] ); + } + } ); +``` + +{@snippet framework/mini-inspector-structure} + + + Using your own custom model element requires defining it in the schema. More information in the example below. + + +## Read next + +{@link framework/guides/deep-dive/conversion-upcast View to model - upcast} diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-intro.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-intro.md new file mode 100644 index 00000000000..ffd20e5bc6e --- /dev/null +++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-intro.md @@ -0,0 +1,36 @@ +--- +category: framework-deep-dive-conversion +menu-title: Introduction +order: 0 +--- + +# Introduction + +## What is the conversion? + +As you may know, the editor works on two layers - model and view. The process of transforming one into the other is called conversion. + +When you load data into the editor, the view is created out of the markup, then, with the help of the upcast converters, the model is created. Once that is done, the model becomes the editor state. + +All changes (e.g. typing or pasting from the clipboard) are then applied directly to the model. In order to update the editing view (the one being displayed to the user), the engine transforms changes in the model to the view. The same process is executed when data needs to be generated (e.g. when you copy editor content or use `editor.getData()`). + +You can think about upcast and downcast as about processes working in opposite directions (symmetrical to each other). + +Following chapters will teach you how to create the right converter for each case, when creating your very own CKEditor 5 plugin. + +## Table of contents + +* **{@link framework/guides/deep-dive/conversion-downcast Model to view - downcast}** + + Model has to be transformed into the view. Learn how to achieve that by creating downcast converters. + +* **{@link framework/guides/deep-dive/conversion-upcast View to model - upcast}** + + Raw data coming into the editor has to be transformed into the model. Learn how to achieve that by creating upcast converters. + +* **{@link framework/guides/deep-dive/conversion-intro Conversion helpers}** + + There are plenty of ways to transform data between model and view. To help you do this as efficiently as possible we provided many functions speeding up this process. This chapter will help you choose the right helper for the job. + + TODO: update this section link. + diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-upcast.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-upcast.md new file mode 100644 index 00000000000..971d4715c16 --- /dev/null +++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-upcast.md @@ -0,0 +1,147 @@ +--- +category: framework-deep-dive-conversion +menu-title: View to model - upcast +order: 2 +--- + +# Model to view - downcast + +## Introduction + +The process of converting the **view** to the **model** is called an **downcast**. + +{@img assets/img/upcast-basic.png 760 Basic downcast conversion diagram.} + +The upcast process conversion happens every time any data is being loaded into the editor. + +Incoming data becomes the view which is then converted into the model via registered converters. + +{@snippet framework/mini-inspector} + +## Registering a converter + +In order to tell the engine how to convert a specific view element into a model element, you need to register an **upcast converter** by using the `editor.conversion.for( 'upcast' )` method: + +```js +editor.conversion + .for( 'upcast' ) + .elementToElement( { + view: 'p', + model: 'paragraph' + } ); +``` + +The above converter will handle the conversion of every `

        ` view element to a `` model element. + +{@snippet framework/mini-inspector-paragraph} + + + This is just an example. Paragraph support is provided by the {@link api/paragraph paragraph plugin} so you don't have to write your own `

        ` element to `` element conversion. + + + + You just learned about the `elementToElement()` **upcast** conversion helper method! More helpers are documented in the following chapters. + + +## Upcast pipeline + +Contrary to the downcast, the upcast process happens only in the data pipeline and is called **data upcast.** + +The editing view may be changed only via changing the model first, hence editing pipeline needs only the downcast process. + +{@img assets/img/upcast-pipeline.png 760 Upcast conversion pipeline diagram.} + +The previous code example registers a converter for both pipelines at once. It means that `` model element will be converted to a `

        ` view element in both **data view** and **editing view**. + +## Converting to text attribute + +View elements representing inline text formatting (such as `` or ``) need to be converted to an attribute on a model text node. + +To register such a converter, use `elementToAttribute()`: + +```js +editor.conversion + .for( 'upcast' ) + .elementToAttribute( { + view: 'strong', + model: 'bold' + } ); +``` + +Text wrapped with the `` tag will be converted to a model text node with a `bold` attribute applied to it. + +{@snippet framework/mini-inspector-bold} + +If you need to “copy” an attribute from a view element to a model element, use `attributeToAttribute()`. + +Keep in mind that the model element must have its own converter registered, otherwise there is nothing the attribute can be copied to. + +```js +editor.conversion + .for( 'upcast' ) + .attributeToAttribute( { + view: 'src', + model: 'source' + } ); +``` + +{@snippet framework/mini-inspector-bold} + +## Converting to element + +Converting a view element to a corresponding model element can be achieved by registering the converter by using the `elementToElement()` method: + +```js +editor.conversion + .for( 'upcast' ) + .elementToElement( { + view: { + name: 'div', + classes: [ 'example' ] + }, + model: 'example' + } ); +``` + +The above converter will handle the conversion of every `

        ` view element into an `` model element. + +{@snippet framework/mini-inspector-bold} + + + Using your own custom model element requires defining it in the schema. More information in the example below. + + +## Converting structures + +As you may learned in the {@link framework/guides/deep-dive/conversion-downcast previous chapter}, a single model element can be downcasted into a structure of multiple view elements. + +The opposite process will have to detect that structure (e.g. the main element) and convert that into a simple model element. + +You can use `elementToStructure()` conversion helper for this purpose: + +```js +editor.conversion.for( 'downcast' ).elementToStructure( { + view: { + name: 'div', + classes: [ 'wrapper' ] + }, + model: ( viewElement, { writer } ) => { + // ... TODO what else needs to happen here? + // * find the

        + // * consume it + // * move cursor to inside

        + + return writer.createElement( 'myElement' ); + } +} ); +``` + +The above converter will detect all `

        ...

        ` structures (by scanning for the outer `
        ` and turn those into a single `` model element. + +{@snippet framework/mini-inspector-structure} + +## Read next + +{@link framework/guides/deep-dive/conversion-intro Conversion helpers} + +TODO: update the link diff --git a/packages/ckeditor5-engine/docs/framework/guides/mini-inspector/miniinspector.js b/packages/ckeditor5-engine/docs/framework/guides/mini-inspector/miniinspector.js new file mode 100644 index 00000000000..f047b74f4f6 --- /dev/null +++ b/packages/ckeditor5-engine/docs/framework/guides/mini-inspector/miniinspector.js @@ -0,0 +1,737 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["MiniCKEditorInspector"] = factory(); + else + root["MiniCKEditorInspector"] = factory(); +})(window, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./src/miniinspector.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/array.js": +/*!******************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/array.js ***! + \******************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.arrayToString = void 0;\n/**\n * Stringify an array of values.\n */\nconst arrayToString = (array, space, next) => {\n // Map array values to their stringified values with correct indentation.\n const values = array\n .map(function (value, index) {\n const result = next(value, index);\n if (result === undefined)\n return String(result);\n return space + result.split(\"\\n\").join(`\\n${space}`);\n })\n .join(space ? \",\\n\" : \",\");\n const eol = space && values ? \"\\n\" : \"\";\n return `[${eol}${values}${eol}]`;\n};\nexports.arrayToString = arrayToString;\n//# sourceMappingURL=array.js.map\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/array.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/function.js": +/*!*********************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/function.js ***! + \*********************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FunctionParser = exports.dedentFunction = exports.functionToString = exports.USED_METHOD_KEY = void 0;\nconst quote_1 = __webpack_require__(/*! ./quote */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js\");\n/**\n * Used in function stringification.\n */\n/* istanbul ignore next */\nconst METHOD_NAMES_ARE_QUOTED = {\n \" \"() {\n /* Empty. */\n },\n}[\" \"]\n .toString()\n .charAt(0) === '\"';\nconst FUNCTION_PREFIXES = {\n Function: \"function \",\n GeneratorFunction: \"function* \",\n AsyncFunction: \"async function \",\n AsyncGeneratorFunction: \"async function* \",\n};\nconst METHOD_PREFIXES = {\n Function: \"\",\n GeneratorFunction: \"*\",\n AsyncFunction: \"async \",\n AsyncGeneratorFunction: \"async *\",\n};\nconst TOKENS_PRECEDING_REGEXPS = new Set((\"case delete else in instanceof new return throw typeof void \" +\n \", ; : + - ! ~ & | ^ * / % < > ? =\").split(\" \"));\n/**\n * Track function parser usage.\n */\nexports.USED_METHOD_KEY = new WeakSet();\n/**\n * Stringify a function.\n */\nconst functionToString = (fn, space, next, key) => {\n const name = typeof key === \"string\" ? key : undefined;\n // Track in function parser for object stringify to avoid duplicate output.\n if (name !== undefined)\n exports.USED_METHOD_KEY.add(fn);\n return new FunctionParser(fn, space, next, name).stringify();\n};\nexports.functionToString = functionToString;\n/**\n * Rewrite a stringified function to remove initial indentation.\n */\nfunction dedentFunction(fnString) {\n let found;\n for (const line of fnString.split(\"\\n\").slice(1)) {\n const m = /^[\\s\\t]+/.exec(line);\n if (!m)\n return fnString; // Early exit without indent.\n const [str] = m;\n if (found === undefined)\n found = str;\n else if (str.length < found.length)\n found = str;\n }\n return found ? fnString.split(`\\n${found}`).join(\"\\n\") : fnString;\n}\nexports.dedentFunction = dedentFunction;\n/**\n * Function parser and stringify.\n */\nclass FunctionParser {\n constructor(fn, indent, next, key) {\n this.fn = fn;\n this.indent = indent;\n this.next = next;\n this.key = key;\n this.pos = 0;\n this.hadKeyword = false;\n this.fnString = Function.prototype.toString.call(fn);\n this.fnType = fn.constructor.name;\n this.keyQuote = key === undefined ? \"\" : quote_1.quoteKey(key, next);\n this.keyPrefix =\n key === undefined ? \"\" : `${this.keyQuote}:${indent ? \" \" : \"\"}`;\n this.isMethodCandidate =\n key === undefined ? false : this.fn.name === \"\" || this.fn.name === key;\n }\n stringify() {\n const value = this.tryParse();\n // If we can't stringify this function, return a void expression; for\n // bonus help with debugging, include the function as a string literal.\n if (!value) {\n return `${this.keyPrefix}void ${this.next(this.fnString)}`;\n }\n return dedentFunction(value);\n }\n getPrefix() {\n if (this.isMethodCandidate && !this.hadKeyword) {\n return METHOD_PREFIXES[this.fnType] + this.keyQuote;\n }\n return this.keyPrefix + FUNCTION_PREFIXES[this.fnType];\n }\n tryParse() {\n if (this.fnString[this.fnString.length - 1] !== \"}\") {\n // Must be an arrow function.\n return this.keyPrefix + this.fnString;\n }\n // Attempt to remove function prefix.\n if (this.fn.name) {\n const result = this.tryStrippingName();\n if (result)\n return result;\n }\n // Support class expressions.\n const prevPos = this.pos;\n if (this.consumeSyntax() === \"class\")\n return this.fnString;\n this.pos = prevPos;\n if (this.tryParsePrefixTokens()) {\n const result = this.tryStrippingName();\n if (result)\n return result;\n let offset = this.pos;\n switch (this.consumeSyntax(\"WORD_LIKE\")) {\n case \"WORD_LIKE\":\n if (this.isMethodCandidate && !this.hadKeyword) {\n offset = this.pos;\n }\n case \"()\":\n if (this.fnString.substr(this.pos, 2) === \"=>\") {\n return this.keyPrefix + this.fnString;\n }\n this.pos = offset;\n case '\"':\n case \"'\":\n case \"[]\":\n return this.getPrefix() + this.fnString.substr(this.pos);\n }\n }\n }\n /**\n * Attempt to parse the function from the current position by first stripping\n * the function's name from the front. This is not a fool-proof method on all\n * JavaScript engines, but yields good results on Node.js 4 (and slightly\n * less good results on Node.js 6 and 8).\n */\n tryStrippingName() {\n if (METHOD_NAMES_ARE_QUOTED) {\n // ... then this approach is unnecessary and yields false positives.\n return;\n }\n let start = this.pos;\n const prefix = this.fnString.substr(this.pos, this.fn.name.length);\n if (prefix === this.fn.name) {\n this.pos += prefix.length;\n if (this.consumeSyntax() === \"()\" &&\n this.consumeSyntax() === \"{}\" &&\n this.pos === this.fnString.length) {\n // Don't include the function's name if it will be included in the\n // prefix, or if it's invalid as a name in a function expression.\n if (this.isMethodCandidate || !quote_1.isValidVariableName(prefix)) {\n start += prefix.length;\n }\n return this.getPrefix() + this.fnString.substr(start);\n }\n }\n this.pos = start;\n }\n /**\n * Attempt to advance the parser past the keywords expected to be at the\n * start of this function's definition. This method sets `this.hadKeyword`\n * based on whether or not a `function` keyword is consumed.\n */\n tryParsePrefixTokens() {\n let posPrev = this.pos;\n this.hadKeyword = false;\n switch (this.fnType) {\n case \"AsyncFunction\":\n if (this.consumeSyntax() !== \"async\")\n return false;\n posPrev = this.pos;\n case \"Function\":\n if (this.consumeSyntax() === \"function\") {\n this.hadKeyword = true;\n }\n else {\n this.pos = posPrev;\n }\n return true;\n case \"AsyncGeneratorFunction\":\n if (this.consumeSyntax() !== \"async\")\n return false;\n case \"GeneratorFunction\":\n let token = this.consumeSyntax();\n if (token === \"function\") {\n token = this.consumeSyntax();\n this.hadKeyword = true;\n }\n return token === \"*\";\n }\n }\n /**\n * Advance the parser past one element of JavaScript syntax. This could be a\n * matched pair of delimiters, like braces or parentheses, or an atomic unit\n * like a keyword, variable, or operator. Return a normalized string\n * representation of the element parsed--for example, returns '{}' for a\n * matched pair of braces. Comments and whitespace are skipped.\n *\n * (This isn't a full parser, so the token scanning logic used here is as\n * simple as it can be. As a consequence, some things that are one token in\n * JavaScript, like decimal number literals or most multi-character operators\n * like '&&', are split into more than one token here. However, awareness of\n * some multi-character sequences like '=>' is necessary, so we match the few\n * of them that we care about.)\n */\n consumeSyntax(wordLikeToken) {\n const m = this.consumeMatch(/^(?:([A-Za-z_0-9$\\xA0-\\uFFFF]+)|=>|\\+\\+|\\-\\-|.)/);\n if (!m)\n return;\n const [token, match] = m;\n this.consumeWhitespace();\n if (match)\n return wordLikeToken || match;\n switch (token) {\n case \"(\":\n return this.consumeSyntaxUntil(\"(\", \")\");\n case \"[\":\n return this.consumeSyntaxUntil(\"[\", \"]\");\n case \"{\":\n return this.consumeSyntaxUntil(\"{\", \"}\");\n case \"`\":\n return this.consumeTemplate();\n case '\"':\n return this.consumeRegExp(/^(?:[^\\\\\"]|\\\\.)*\"/, '\"');\n case \"'\":\n return this.consumeRegExp(/^(?:[^\\\\']|\\\\.)*'/, \"'\");\n }\n return token;\n }\n consumeSyntaxUntil(startToken, endToken) {\n let isRegExpAllowed = true;\n for (;;) {\n const token = this.consumeSyntax();\n if (token === endToken)\n return startToken + endToken;\n if (!token || token === \")\" || token === \"]\" || token === \"}\")\n return;\n if (token === \"/\" &&\n isRegExpAllowed &&\n this.consumeMatch(/^(?:\\\\.|[^\\\\\\/\\n[]|\\[(?:\\\\.|[^\\]])*\\])+\\/[a-z]*/)) {\n isRegExpAllowed = false;\n this.consumeWhitespace();\n }\n else {\n isRegExpAllowed = TOKENS_PRECEDING_REGEXPS.has(token);\n }\n }\n }\n consumeMatch(re) {\n const m = re.exec(this.fnString.substr(this.pos));\n if (m)\n this.pos += m[0].length;\n return m;\n }\n /**\n * Advance the parser past an arbitrary regular expression. Return `token`,\n * or the match object of the regexp.\n */\n consumeRegExp(re, token) {\n const m = re.exec(this.fnString.substr(this.pos));\n if (!m)\n return;\n this.pos += m[0].length;\n this.consumeWhitespace();\n return token;\n }\n /**\n * Advance the parser past a template string.\n */\n consumeTemplate() {\n for (;;) {\n this.consumeMatch(/^(?:[^`$\\\\]|\\\\.|\\$(?!{))*/);\n if (this.fnString[this.pos] === \"`\") {\n this.pos++;\n this.consumeWhitespace();\n return \"`\";\n }\n if (this.fnString.substr(this.pos, 2) === \"${\") {\n this.pos += 2;\n this.consumeWhitespace();\n if (this.consumeSyntaxUntil(\"{\", \"}\"))\n continue;\n }\n return;\n }\n }\n /**\n * Advance the parser past any whitespace or comments.\n */\n consumeWhitespace() {\n this.consumeMatch(/^(?:\\s|\\/\\/.*|\\/\\*[^]*?\\*\\/)*/);\n }\n}\nexports.FunctionParser = FunctionParser;\n//# sourceMappingURL=function.js.map\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/function.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/index.js": +/*!******************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/index.js ***! + \******************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.stringify = void 0;\nconst stringify_1 = __webpack_require__(/*! ./stringify */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/stringify.js\");\nconst quote_1 = __webpack_require__(/*! ./quote */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js\");\n/**\n * Root path node.\n */\nconst ROOT_SENTINEL = Symbol(\"root\");\n/**\n * Stringify any JavaScript value.\n */\nfunction stringify(value, replacer, indent, options = {}) {\n const space = typeof indent === \"string\" ? indent : \" \".repeat(indent || 0);\n const path = [];\n const stack = new Set();\n const tracking = new Map();\n const unpack = new Map();\n let valueCount = 0;\n const { maxDepth = 100, references = false, skipUndefinedProperties = false, maxValues = 100000, } = options;\n // Wrap replacer function to support falling back on supported stringify.\n const valueToString = replacerToString(replacer);\n // Every time you call `next(value)` execute this function.\n const onNext = (value, key) => {\n if (++valueCount > maxValues)\n return;\n if (skipUndefinedProperties && value === undefined)\n return;\n if (path.length > maxDepth)\n return;\n // An undefined key is treated as an out-of-band \"value\".\n if (key === undefined)\n return valueToString(value, space, onNext, key);\n path.push(key);\n const result = builder(value, key === ROOT_SENTINEL ? undefined : key);\n path.pop();\n return result;\n };\n const builder = references\n ? (value, key) => {\n if (value !== null &&\n (typeof value === \"object\" ||\n typeof value === \"function\" ||\n typeof value === \"symbol\")) {\n // Track nodes to restore later.\n if (tracking.has(value)) {\n unpack.set(path.slice(1), tracking.get(value));\n // Use `undefined` as temporaray stand-in for referenced nodes\n return valueToString(undefined, space, onNext, key);\n }\n // Track encountered nodes.\n tracking.set(value, path.slice(1));\n }\n return valueToString(value, space, onNext, key);\n }\n : (value, key) => {\n // Stop on recursion.\n if (stack.has(value))\n return;\n stack.add(value);\n const result = valueToString(value, space, onNext, key);\n stack.delete(value);\n return result;\n };\n const result = onNext(value, ROOT_SENTINEL);\n // Attempt to restore circular references.\n if (unpack.size) {\n const sp = space ? \" \" : \"\";\n const eol = space ? \"\\n\" : \"\";\n let wrapper = `var x${sp}=${sp}${result};${eol}`;\n for (const [key, value] of unpack.entries()) {\n const keyPath = quote_1.stringifyPath(key, onNext);\n const valuePath = quote_1.stringifyPath(value, onNext);\n wrapper += `x${keyPath}${sp}=${sp}x${valuePath};${eol}`;\n }\n return `(function${sp}()${sp}{${eol}${wrapper}return x;${eol}}())`;\n }\n return result;\n}\nexports.stringify = stringify;\n/**\n * Create `toString()` function from replacer.\n */\nfunction replacerToString(replacer) {\n if (!replacer)\n return stringify_1.toString;\n return (value, space, next, key) => {\n return replacer(value, space, (value) => stringify_1.toString(value, space, next, key), key);\n };\n}\n//# sourceMappingURL=index.js.map\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/index.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/object.js": +/*!*******************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/object.js ***! + \*******************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/* WEBPACK VAR INJECTION */(function(Buffer, global) {\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.objectToString = void 0;\nconst quote_1 = __webpack_require__(/*! ./quote */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js\");\nconst function_1 = __webpack_require__(/*! ./function */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/function.js\");\nconst array_1 = __webpack_require__(/*! ./array */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/array.js\");\n/**\n * Transform an object into a string.\n */\nconst objectToString = (value, space, next, key) => {\n // Support buffer in all environments.\n if (typeof Buffer === \"function\" && Buffer.isBuffer(value)) {\n return `Buffer.from(${next(value.toString(\"base64\"))}, 'base64')`;\n }\n // Support `global` under test environments that don't print `[object global]`.\n if (typeof global === \"object\" && value === global) {\n return globalToString(value, space, next, key);\n }\n // Use the internal object string to select stringify method.\n const toString = OBJECT_TYPES[Object.prototype.toString.call(value)];\n return toString ? toString(value, space, next, key) : undefined;\n};\nexports.objectToString = objectToString;\n/**\n * Stringify an object of keys and values.\n */\nconst rawObjectToString = (obj, indent, next, key) => {\n const eol = indent ? \"\\n\" : \"\";\n const space = indent ? \" \" : \"\";\n // Iterate over object keys and concat string together.\n const values = Object.keys(obj)\n .reduce(function (values, key) {\n const fn = obj[key];\n const result = next(fn, key);\n // Omit `undefined` object entries.\n if (result === undefined)\n return values;\n // String format the value data.\n const value = result.split(\"\\n\").join(`\\n${indent}`);\n // Skip `key` prefix for function parser.\n if (function_1.USED_METHOD_KEY.has(fn)) {\n values.push(`${indent}${value}`);\n return values;\n }\n values.push(`${indent}${quote_1.quoteKey(key, next)}:${space}${value}`);\n return values;\n }, [])\n .join(`,${eol}`);\n // Avoid new lines in an empty object.\n if (values === \"\")\n return \"{}\";\n return `{${eol}${values}${eol}}`;\n};\n/**\n * Stringify global variable access.\n */\nconst globalToString = (value, space, next) => {\n return `Function(${next(\"return this\")})()`;\n};\n/**\n * Convert JavaScript objects into strings.\n */\nconst OBJECT_TYPES = {\n \"[object Array]\": array_1.arrayToString,\n \"[object Object]\": rawObjectToString,\n \"[object Error]\": (error, space, next) => {\n return `new Error(${next(error.message)})`;\n },\n \"[object Date]\": (date) => {\n return `new Date(${date.getTime()})`;\n },\n \"[object String]\": (str, space, next) => {\n return `new String(${next(str.toString())})`;\n },\n \"[object Number]\": (num) => {\n return `new Number(${num})`;\n },\n \"[object Boolean]\": (bool) => {\n return `new Boolean(${bool})`;\n },\n \"[object Set]\": (set, space, next) => {\n return `new Set(${next(Array.from(set))})`;\n },\n \"[object Map]\": (map, space, next) => {\n return `new Map(${next(Array.from(map))})`;\n },\n \"[object RegExp]\": String,\n \"[object global]\": globalToString,\n \"[object Window]\": globalToString,\n};\n//# sourceMappingURL=object.js.map\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../../../ckeditor5-mini-inspector/node_modules/buffer/index.js */ \"./node_modules/buffer/index.js\").Buffer, __webpack_require__(/*! ./../../../../ckeditor5-mini-inspector/node_modules/webpack/buildin/global.js */ \"./node_modules/webpack/buildin/global.js\")))\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/object.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js": +/*!******************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js ***! + \******************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.stringifyPath = exports.quoteKey = exports.isValidVariableName = exports.IS_VALID_IDENTIFIER = exports.quoteString = void 0;\n/**\n * Match all characters that need to be escaped in a string. Modified from\n * source to match single quotes instead of double.\n *\n * Source: https://github.com/douglascrockford/JSON-js/blob/master/json2.js\n */\nconst ESCAPABLE = /[\\\\\\'\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n/**\n * Map of characters to escape characters.\n */\nconst META_CHARS = new Map([\n [\"\\b\", \"\\\\b\"],\n [\"\\t\", \"\\\\t\"],\n [\"\\n\", \"\\\\n\"],\n [\"\\f\", \"\\\\f\"],\n [\"\\r\", \"\\\\r\"],\n [\"'\", \"\\\\'\"],\n ['\"', '\\\\\"'],\n [\"\\\\\", \"\\\\\\\\\"],\n]);\n/**\n * Escape any character into its literal JavaScript string.\n *\n * @param {string} char\n * @return {string}\n */\nfunction escapeChar(char) {\n return (META_CHARS.get(char) ||\n `\\\\u${`0000${char.charCodeAt(0).toString(16)}`.slice(-4)}`);\n}\n/**\n * Quote a string.\n */\nfunction quoteString(str) {\n return `'${str.replace(ESCAPABLE, escapeChar)}'`;\n}\nexports.quoteString = quoteString;\n/**\n * JavaScript reserved keywords.\n */\nconst RESERVED_WORDS = new Set((\"break else new var case finally return void catch for switch while \" +\n \"continue function this with default if throw delete in try \" +\n \"do instanceof typeof abstract enum int short boolean export \" +\n \"interface static byte extends long super char final native synchronized \" +\n \"class float package throws const goto private transient debugger \" +\n \"implements protected volatile double import public let yield\").split(\" \"));\n/**\n * Test for valid JavaScript identifier.\n */\nexports.IS_VALID_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n/**\n * Check if a variable name is valid.\n */\nfunction isValidVariableName(name) {\n return (typeof name === \"string\" &&\n !RESERVED_WORDS.has(name) &&\n exports.IS_VALID_IDENTIFIER.test(name));\n}\nexports.isValidVariableName = isValidVariableName;\n/**\n * Quote JavaScript key access.\n */\nfunction quoteKey(key, next) {\n return isValidVariableName(key) ? key : next(key);\n}\nexports.quoteKey = quoteKey;\n/**\n * Serialize the path to a string.\n */\nfunction stringifyPath(path, next) {\n let result = \"\";\n for (const key of path) {\n if (isValidVariableName(key)) {\n result += `.${key}`;\n }\n else {\n result += `[${next(key)}]`;\n }\n }\n return result;\n}\nexports.stringifyPath = stringifyPath;\n//# sourceMappingURL=quote.js.map\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/javascript-stringify/dist/stringify.js": +/*!**********************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/javascript-stringify/dist/stringify.js ***! + \**********************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.toString = void 0;\nconst quote_1 = __webpack_require__(/*! ./quote */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/quote.js\");\nconst object_1 = __webpack_require__(/*! ./object */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/object.js\");\nconst function_1 = __webpack_require__(/*! ./function */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/function.js\");\n/**\n * Stringify primitive values.\n */\nconst PRIMITIVE_TYPES = {\n string: quote_1.quoteString,\n number: (value) => (Object.is(value, -0) ? \"-0\" : String(value)),\n boolean: String,\n symbol: (value, space, next) => {\n const key = Symbol.keyFor(value);\n if (key !== undefined)\n return `Symbol.for(${next(key)})`;\n // ES2018 `Symbol.description`.\n return `Symbol(${next(value.description)})`;\n },\n bigint: (value, space, next) => {\n return `BigInt(${next(String(value))})`;\n },\n undefined: String,\n object: object_1.objectToString,\n function: function_1.functionToString,\n};\n/**\n * Stringify a value recursively.\n */\nconst toString = (value, space, next, key) => {\n if (value === null)\n return \"null\";\n return PRIMITIVE_TYPES[typeof value](value, space, next, key);\n};\nexports.toString = toString;\n//# sourceMappingURL=stringify.js.map\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/javascript-stringify/dist/stringify.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/lodash.isequal/index.js": +/*!*******************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/lodash.isequal/index.js ***! + \*******************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("/* WEBPACK VAR INJECTION */(function(global, module) {/**\n * Lodash (Custom Build) \n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright JS Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n\n/** Used as the size to enable large array optimizations. */\nvar LARGE_ARRAY_SIZE = 200;\n\n/** Used to stand-in for `undefined` hash values. */\nvar HASH_UNDEFINED = '__lodash_hash_undefined__';\n\n/** Used to compose bitmasks for value comparisons. */\nvar COMPARE_PARTIAL_FLAG = 1,\n COMPARE_UNORDERED_FLAG = 2;\n\n/** Used as references for various `Number` constants. */\nvar MAX_SAFE_INTEGER = 9007199254740991;\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n arrayTag = '[object Array]',\n asyncTag = '[object AsyncFunction]',\n boolTag = '[object Boolean]',\n dateTag = '[object Date]',\n errorTag = '[object Error]',\n funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n mapTag = '[object Map]',\n numberTag = '[object Number]',\n nullTag = '[object Null]',\n objectTag = '[object Object]',\n promiseTag = '[object Promise]',\n proxyTag = '[object Proxy]',\n regexpTag = '[object RegExp]',\n setTag = '[object Set]',\n stringTag = '[object String]',\n symbolTag = '[object Symbol]',\n undefinedTag = '[object Undefined]',\n weakMapTag = '[object WeakMap]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n dataViewTag = '[object DataView]',\n float32Tag = '[object Float32Array]',\n float64Tag = '[object Float64Array]',\n int8Tag = '[object Int8Array]',\n int16Tag = '[object Int16Array]',\n int32Tag = '[object Int32Array]',\n uint8Tag = '[object Uint8Array]',\n uint8ClampedTag = '[object Uint8ClampedArray]',\n uint16Tag = '[object Uint16Array]',\n uint32Tag = '[object Uint32Array]';\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used to detect unsigned integer values. */\nvar reIsUint = /^(?:0|[1-9]\\d*)$/;\n\n/** Used to identify `toStringTag` values of typed arrays. */\nvar typedArrayTags = {};\ntypedArrayTags[float32Tag] = typedArrayTags[float64Tag] =\ntypedArrayTags[int8Tag] = typedArrayTags[int16Tag] =\ntypedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =\ntypedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =\ntypedArrayTags[uint32Tag] = true;\ntypedArrayTags[argsTag] = typedArrayTags[arrayTag] =\ntypedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =\ntypedArrayTags[dataViewTag] = typedArrayTags[dateTag] =\ntypedArrayTags[errorTag] = typedArrayTags[funcTag] =\ntypedArrayTags[mapTag] = typedArrayTags[numberTag] =\ntypedArrayTags[objectTag] = typedArrayTags[regexpTag] =\ntypedArrayTags[setTag] = typedArrayTags[stringTag] =\ntypedArrayTags[weakMapTag] = false;\n\n/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\n/** Detect free variable `exports`. */\nvar freeExports = true && exports && !exports.nodeType && exports;\n\n/** Detect free variable `module`. */\nvar freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;\n\n/** Detect the popular CommonJS extension `module.exports`. */\nvar moduleExports = freeModule && freeModule.exports === freeExports;\n\n/** Detect free variable `process` from Node.js. */\nvar freeProcess = moduleExports && freeGlobal.process;\n\n/** Used to access faster Node.js helpers. */\nvar nodeUtil = (function() {\n try {\n return freeProcess && freeProcess.binding && freeProcess.binding('util');\n } catch (e) {}\n}());\n\n/* Node.js helper references. */\nvar nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;\n\n/**\n * A specialized version of `_.filter` for arrays without support for\n * iteratee shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} predicate The function invoked per iteration.\n * @returns {Array} Returns the new filtered array.\n */\nfunction arrayFilter(array, predicate) {\n var index = -1,\n length = array == null ? 0 : array.length,\n resIndex = 0,\n result = [];\n\n while (++index < length) {\n var value = array[index];\n if (predicate(value, index, array)) {\n result[resIndex++] = value;\n }\n }\n return result;\n}\n\n/**\n * Appends the elements of `values` to `array`.\n *\n * @private\n * @param {Array} array The array to modify.\n * @param {Array} values The values to append.\n * @returns {Array} Returns `array`.\n */\nfunction arrayPush(array, values) {\n var index = -1,\n length = values.length,\n offset = array.length;\n\n while (++index < length) {\n array[offset + index] = values[index];\n }\n return array;\n}\n\n/**\n * A specialized version of `_.some` for arrays without support for iteratee\n * shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} predicate The function invoked per iteration.\n * @returns {boolean} Returns `true` if any element passes the predicate check,\n * else `false`.\n */\nfunction arraySome(array, predicate) {\n var index = -1,\n length = array == null ? 0 : array.length;\n\n while (++index < length) {\n if (predicate(array[index], index, array)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * The base implementation of `_.times` without support for iteratee shorthands\n * or max array length checks.\n *\n * @private\n * @param {number} n The number of times to invoke `iteratee`.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns the array of results.\n */\nfunction baseTimes(n, iteratee) {\n var index = -1,\n result = Array(n);\n\n while (++index < n) {\n result[index] = iteratee(index);\n }\n return result;\n}\n\n/**\n * The base implementation of `_.unary` without support for storing metadata.\n *\n * @private\n * @param {Function} func The function to cap arguments for.\n * @returns {Function} Returns the new capped function.\n */\nfunction baseUnary(func) {\n return function(value) {\n return func(value);\n };\n}\n\n/**\n * Checks if a `cache` value for `key` exists.\n *\n * @private\n * @param {Object} cache The cache to query.\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction cacheHas(cache, key) {\n return cache.has(key);\n}\n\n/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\n/**\n * Converts `map` to its key-value pairs.\n *\n * @private\n * @param {Object} map The map to convert.\n * @returns {Array} Returns the key-value pairs.\n */\nfunction mapToArray(map) {\n var index = -1,\n result = Array(map.size);\n\n map.forEach(function(value, key) {\n result[++index] = [key, value];\n });\n return result;\n}\n\n/**\n * Creates a unary function that invokes `func` with its argument transformed.\n *\n * @private\n * @param {Function} func The function to wrap.\n * @param {Function} transform The argument transform.\n * @returns {Function} Returns the new function.\n */\nfunction overArg(func, transform) {\n return function(arg) {\n return func(transform(arg));\n };\n}\n\n/**\n * Converts `set` to an array of its values.\n *\n * @private\n * @param {Object} set The set to convert.\n * @returns {Array} Returns the values.\n */\nfunction setToArray(set) {\n var index = -1,\n result = Array(set.size);\n\n set.forEach(function(value) {\n result[++index] = value;\n });\n return result;\n}\n\n/** Used for built-in method references. */\nvar arrayProto = Array.prototype,\n funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/** Built-in value references. */\nvar Buffer = moduleExports ? root.Buffer : undefined,\n Symbol = root.Symbol,\n Uint8Array = root.Uint8Array,\n propertyIsEnumerable = objectProto.propertyIsEnumerable,\n splice = arrayProto.splice,\n symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeGetSymbols = Object.getOwnPropertySymbols,\n nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,\n nativeKeys = overArg(Object.keys, Object);\n\n/* Built-in method references that are verified to be native. */\nvar DataView = getNative(root, 'DataView'),\n Map = getNative(root, 'Map'),\n Promise = getNative(root, 'Promise'),\n Set = getNative(root, 'Set'),\n WeakMap = getNative(root, 'WeakMap'),\n nativeCreate = getNative(Object, 'create');\n\n/** Used to detect maps, sets, and weakmaps. */\nvar dataViewCtorString = toSource(DataView),\n mapCtorString = toSource(Map),\n promiseCtorString = toSource(Promise),\n setCtorString = toSource(Set),\n weakMapCtorString = toSource(WeakMap);\n\n/** Used to convert symbols to primitives and strings. */\nvar symbolProto = Symbol ? Symbol.prototype : undefined,\n symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;\n\n/**\n * Creates a hash object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Hash(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the hash.\n *\n * @private\n * @name clear\n * @memberOf Hash\n */\nfunction hashClear() {\n this.__data__ = nativeCreate ? nativeCreate(null) : {};\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the hash.\n *\n * @private\n * @name delete\n * @memberOf Hash\n * @param {Object} hash The hash to modify.\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction hashDelete(key) {\n var result = this.has(key) && delete this.__data__[key];\n this.size -= result ? 1 : 0;\n return result;\n}\n\n/**\n * Gets the hash value for `key`.\n *\n * @private\n * @name get\n * @memberOf Hash\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction hashGet(key) {\n var data = this.__data__;\n if (nativeCreate) {\n var result = data[key];\n return result === HASH_UNDEFINED ? undefined : result;\n }\n return hasOwnProperty.call(data, key) ? data[key] : undefined;\n}\n\n/**\n * Checks if a hash value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Hash\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction hashHas(key) {\n var data = this.__data__;\n return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);\n}\n\n/**\n * Sets the hash `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Hash\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the hash instance.\n */\nfunction hashSet(key, value) {\n var data = this.__data__;\n this.size += this.has(key) ? 0 : 1;\n data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;\n return this;\n}\n\n// Add methods to `Hash`.\nHash.prototype.clear = hashClear;\nHash.prototype['delete'] = hashDelete;\nHash.prototype.get = hashGet;\nHash.prototype.has = hashHas;\nHash.prototype.set = hashSet;\n\n/**\n * Creates an list cache object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction ListCache(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the list cache.\n *\n * @private\n * @name clear\n * @memberOf ListCache\n */\nfunction listCacheClear() {\n this.__data__ = [];\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the list cache.\n *\n * @private\n * @name delete\n * @memberOf ListCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction listCacheDelete(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n return false;\n }\n var lastIndex = data.length - 1;\n if (index == lastIndex) {\n data.pop();\n } else {\n splice.call(data, index, 1);\n }\n --this.size;\n return true;\n}\n\n/**\n * Gets the list cache value for `key`.\n *\n * @private\n * @name get\n * @memberOf ListCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction listCacheGet(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n return index < 0 ? undefined : data[index][1];\n}\n\n/**\n * Checks if a list cache value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf ListCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction listCacheHas(key) {\n return assocIndexOf(this.__data__, key) > -1;\n}\n\n/**\n * Sets the list cache `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf ListCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the list cache instance.\n */\nfunction listCacheSet(key, value) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n ++this.size;\n data.push([key, value]);\n } else {\n data[index][1] = value;\n }\n return this;\n}\n\n// Add methods to `ListCache`.\nListCache.prototype.clear = listCacheClear;\nListCache.prototype['delete'] = listCacheDelete;\nListCache.prototype.get = listCacheGet;\nListCache.prototype.has = listCacheHas;\nListCache.prototype.set = listCacheSet;\n\n/**\n * Creates a map cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction MapCache(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the map.\n *\n * @private\n * @name clear\n * @memberOf MapCache\n */\nfunction mapCacheClear() {\n this.size = 0;\n this.__data__ = {\n 'hash': new Hash,\n 'map': new (Map || ListCache),\n 'string': new Hash\n };\n}\n\n/**\n * Removes `key` and its value from the map.\n *\n * @private\n * @name delete\n * @memberOf MapCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction mapCacheDelete(key) {\n var result = getMapData(this, key)['delete'](key);\n this.size -= result ? 1 : 0;\n return result;\n}\n\n/**\n * Gets the map value for `key`.\n *\n * @private\n * @name get\n * @memberOf MapCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction mapCacheGet(key) {\n return getMapData(this, key).get(key);\n}\n\n/**\n * Checks if a map value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf MapCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction mapCacheHas(key) {\n return getMapData(this, key).has(key);\n}\n\n/**\n * Sets the map `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf MapCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the map cache instance.\n */\nfunction mapCacheSet(key, value) {\n var data = getMapData(this, key),\n size = data.size;\n\n data.set(key, value);\n this.size += data.size == size ? 0 : 1;\n return this;\n}\n\n// Add methods to `MapCache`.\nMapCache.prototype.clear = mapCacheClear;\nMapCache.prototype['delete'] = mapCacheDelete;\nMapCache.prototype.get = mapCacheGet;\nMapCache.prototype.has = mapCacheHas;\nMapCache.prototype.set = mapCacheSet;\n\n/**\n *\n * Creates an array cache object to store unique values.\n *\n * @private\n * @constructor\n * @param {Array} [values] The values to cache.\n */\nfunction SetCache(values) {\n var index = -1,\n length = values == null ? 0 : values.length;\n\n this.__data__ = new MapCache;\n while (++index < length) {\n this.add(values[index]);\n }\n}\n\n/**\n * Adds `value` to the array cache.\n *\n * @private\n * @name add\n * @memberOf SetCache\n * @alias push\n * @param {*} value The value to cache.\n * @returns {Object} Returns the cache instance.\n */\nfunction setCacheAdd(value) {\n this.__data__.set(value, HASH_UNDEFINED);\n return this;\n}\n\n/**\n * Checks if `value` is in the array cache.\n *\n * @private\n * @name has\n * @memberOf SetCache\n * @param {*} value The value to search for.\n * @returns {number} Returns `true` if `value` is found, else `false`.\n */\nfunction setCacheHas(value) {\n return this.__data__.has(value);\n}\n\n// Add methods to `SetCache`.\nSetCache.prototype.add = SetCache.prototype.push = setCacheAdd;\nSetCache.prototype.has = setCacheHas;\n\n/**\n * Creates a stack cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Stack(entries) {\n var data = this.__data__ = new ListCache(entries);\n this.size = data.size;\n}\n\n/**\n * Removes all key-value entries from the stack.\n *\n * @private\n * @name clear\n * @memberOf Stack\n */\nfunction stackClear() {\n this.__data__ = new ListCache;\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the stack.\n *\n * @private\n * @name delete\n * @memberOf Stack\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction stackDelete(key) {\n var data = this.__data__,\n result = data['delete'](key);\n\n this.size = data.size;\n return result;\n}\n\n/**\n * Gets the stack value for `key`.\n *\n * @private\n * @name get\n * @memberOf Stack\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction stackGet(key) {\n return this.__data__.get(key);\n}\n\n/**\n * Checks if a stack value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Stack\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction stackHas(key) {\n return this.__data__.has(key);\n}\n\n/**\n * Sets the stack `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Stack\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the stack cache instance.\n */\nfunction stackSet(key, value) {\n var data = this.__data__;\n if (data instanceof ListCache) {\n var pairs = data.__data__;\n if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {\n pairs.push([key, value]);\n this.size = ++data.size;\n return this;\n }\n data = this.__data__ = new MapCache(pairs);\n }\n data.set(key, value);\n this.size = data.size;\n return this;\n}\n\n// Add methods to `Stack`.\nStack.prototype.clear = stackClear;\nStack.prototype['delete'] = stackDelete;\nStack.prototype.get = stackGet;\nStack.prototype.has = stackHas;\nStack.prototype.set = stackSet;\n\n/**\n * Creates an array of the enumerable property names of the array-like `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @param {boolean} inherited Specify returning inherited property names.\n * @returns {Array} Returns the array of property names.\n */\nfunction arrayLikeKeys(value, inherited) {\n var isArr = isArray(value),\n isArg = !isArr && isArguments(value),\n isBuff = !isArr && !isArg && isBuffer(value),\n isType = !isArr && !isArg && !isBuff && isTypedArray(value),\n skipIndexes = isArr || isArg || isBuff || isType,\n result = skipIndexes ? baseTimes(value.length, String) : [],\n length = result.length;\n\n for (var key in value) {\n if ((inherited || hasOwnProperty.call(value, key)) &&\n !(skipIndexes && (\n // Safari 9 has enumerable `arguments.length` in strict mode.\n key == 'length' ||\n // Node.js 0.10 has enumerable non-index properties on buffers.\n (isBuff && (key == 'offset' || key == 'parent')) ||\n // PhantomJS 2 has enumerable non-index properties on typed arrays.\n (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||\n // Skip index properties.\n isIndex(key, length)\n ))) {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * Gets the index at which the `key` is found in `array` of key-value pairs.\n *\n * @private\n * @param {Array} array The array to inspect.\n * @param {*} key The key to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction assocIndexOf(array, key) {\n var length = array.length;\n while (length--) {\n if (eq(array[length][0], key)) {\n return length;\n }\n }\n return -1;\n}\n\n/**\n * The base implementation of `getAllKeys` and `getAllKeysIn` which uses\n * `keysFunc` and `symbolsFunc` to get the enumerable property names and\n * symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Function} keysFunc The function to get the keys of `object`.\n * @param {Function} symbolsFunc The function to get the symbols of `object`.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction baseGetAllKeys(object, keysFunc, symbolsFunc) {\n var result = keysFunc(object);\n return isArray(object) ? result : arrayPush(result, symbolsFunc(object));\n}\n\n/**\n * The base implementation of `getTag` without fallbacks for buggy environments.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nfunction baseGetTag(value) {\n if (value == null) {\n return value === undefined ? undefinedTag : nullTag;\n }\n return (symToStringTag && symToStringTag in Object(value))\n ? getRawTag(value)\n : objectToString(value);\n}\n\n/**\n * The base implementation of `_.isArguments`.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an `arguments` object,\n */\nfunction baseIsArguments(value) {\n return isObjectLike(value) && baseGetTag(value) == argsTag;\n}\n\n/**\n * The base implementation of `_.isEqual` which supports partial comparisons\n * and tracks traversed objects.\n *\n * @private\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @param {boolean} bitmask The bitmask flags.\n * 1 - Unordered comparison\n * 2 - Partial comparison\n * @param {Function} [customizer] The function to customize comparisons.\n * @param {Object} [stack] Tracks traversed `value` and `other` objects.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n */\nfunction baseIsEqual(value, other, bitmask, customizer, stack) {\n if (value === other) {\n return true;\n }\n if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {\n return value !== value && other !== other;\n }\n return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);\n}\n\n/**\n * A specialized version of `baseIsEqual` for arrays and objects which performs\n * deep comparisons and tracks traversed objects enabling objects with circular\n * references to be compared.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} [stack] Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {\n var objIsArr = isArray(object),\n othIsArr = isArray(other),\n objTag = objIsArr ? arrayTag : getTag(object),\n othTag = othIsArr ? arrayTag : getTag(other);\n\n objTag = objTag == argsTag ? objectTag : objTag;\n othTag = othTag == argsTag ? objectTag : othTag;\n\n var objIsObj = objTag == objectTag,\n othIsObj = othTag == objectTag,\n isSameTag = objTag == othTag;\n\n if (isSameTag && isBuffer(object)) {\n if (!isBuffer(other)) {\n return false;\n }\n objIsArr = true;\n objIsObj = false;\n }\n if (isSameTag && !objIsObj) {\n stack || (stack = new Stack);\n return (objIsArr || isTypedArray(object))\n ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)\n : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);\n }\n if (!(bitmask & COMPARE_PARTIAL_FLAG)) {\n var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),\n othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');\n\n if (objIsWrapped || othIsWrapped) {\n var objUnwrapped = objIsWrapped ? object.value() : object,\n othUnwrapped = othIsWrapped ? other.value() : other;\n\n stack || (stack = new Stack);\n return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);\n }\n }\n if (!isSameTag) {\n return false;\n }\n stack || (stack = new Stack);\n return equalObjects(object, other, bitmask, customizer, equalFunc, stack);\n}\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = isFunction(value) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\n/**\n * The base implementation of `_.isTypedArray` without Node.js optimizations.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.\n */\nfunction baseIsTypedArray(value) {\n return isObjectLike(value) &&\n isLength(value.length) && !!typedArrayTags[baseGetTag(value)];\n}\n\n/**\n * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n */\nfunction baseKeys(object) {\n if (!isPrototype(object)) {\n return nativeKeys(object);\n }\n var result = [];\n for (var key in Object(object)) {\n if (hasOwnProperty.call(object, key) && key != 'constructor') {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for arrays with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Array} array The array to compare.\n * @param {Array} other The other array to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `array` and `other` objects.\n * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.\n */\nfunction equalArrays(array, other, bitmask, customizer, equalFunc, stack) {\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG,\n arrLength = array.length,\n othLength = other.length;\n\n if (arrLength != othLength && !(isPartial && othLength > arrLength)) {\n return false;\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(array);\n if (stacked && stack.get(other)) {\n return stacked == other;\n }\n var index = -1,\n result = true,\n seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;\n\n stack.set(array, other);\n stack.set(other, array);\n\n // Ignore non-index properties.\n while (++index < arrLength) {\n var arrValue = array[index],\n othValue = other[index];\n\n if (customizer) {\n var compared = isPartial\n ? customizer(othValue, arrValue, index, other, array, stack)\n : customizer(arrValue, othValue, index, array, other, stack);\n }\n if (compared !== undefined) {\n if (compared) {\n continue;\n }\n result = false;\n break;\n }\n // Recursively compare arrays (susceptible to call stack limits).\n if (seen) {\n if (!arraySome(other, function(othValue, othIndex) {\n if (!cacheHas(seen, othIndex) &&\n (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {\n return seen.push(othIndex);\n }\n })) {\n result = false;\n break;\n }\n } else if (!(\n arrValue === othValue ||\n equalFunc(arrValue, othValue, bitmask, customizer, stack)\n )) {\n result = false;\n break;\n }\n }\n stack['delete'](array);\n stack['delete'](other);\n return result;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for comparing objects of\n * the same `toStringTag`.\n *\n * **Note:** This function only supports comparing values with tags of\n * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {string} tag The `toStringTag` of the objects to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {\n switch (tag) {\n case dataViewTag:\n if ((object.byteLength != other.byteLength) ||\n (object.byteOffset != other.byteOffset)) {\n return false;\n }\n object = object.buffer;\n other = other.buffer;\n\n case arrayBufferTag:\n if ((object.byteLength != other.byteLength) ||\n !equalFunc(new Uint8Array(object), new Uint8Array(other))) {\n return false;\n }\n return true;\n\n case boolTag:\n case dateTag:\n case numberTag:\n // Coerce booleans to `1` or `0` and dates to milliseconds.\n // Invalid dates are coerced to `NaN`.\n return eq(+object, +other);\n\n case errorTag:\n return object.name == other.name && object.message == other.message;\n\n case regexpTag:\n case stringTag:\n // Coerce regexes to strings and treat strings, primitives and objects,\n // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring\n // for more details.\n return object == (other + '');\n\n case mapTag:\n var convert = mapToArray;\n\n case setTag:\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG;\n convert || (convert = setToArray);\n\n if (object.size != other.size && !isPartial) {\n return false;\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(object);\n if (stacked) {\n return stacked == other;\n }\n bitmask |= COMPARE_UNORDERED_FLAG;\n\n // Recursively compare objects (susceptible to call stack limits).\n stack.set(object, other);\n var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);\n stack['delete'](object);\n return result;\n\n case symbolTag:\n if (symbolValueOf) {\n return symbolValueOf.call(object) == symbolValueOf.call(other);\n }\n }\n return false;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for objects with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalObjects(object, other, bitmask, customizer, equalFunc, stack) {\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG,\n objProps = getAllKeys(object),\n objLength = objProps.length,\n othProps = getAllKeys(other),\n othLength = othProps.length;\n\n if (objLength != othLength && !isPartial) {\n return false;\n }\n var index = objLength;\n while (index--) {\n var key = objProps[index];\n if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {\n return false;\n }\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(object);\n if (stacked && stack.get(other)) {\n return stacked == other;\n }\n var result = true;\n stack.set(object, other);\n stack.set(other, object);\n\n var skipCtor = isPartial;\n while (++index < objLength) {\n key = objProps[index];\n var objValue = object[key],\n othValue = other[key];\n\n if (customizer) {\n var compared = isPartial\n ? customizer(othValue, objValue, key, other, object, stack)\n : customizer(objValue, othValue, key, object, other, stack);\n }\n // Recursively compare objects (susceptible to call stack limits).\n if (!(compared === undefined\n ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))\n : compared\n )) {\n result = false;\n break;\n }\n skipCtor || (skipCtor = key == 'constructor');\n }\n if (result && !skipCtor) {\n var objCtor = object.constructor,\n othCtor = other.constructor;\n\n // Non `Object` object instances with different constructors are not equal.\n if (objCtor != othCtor &&\n ('constructor' in object && 'constructor' in other) &&\n !(typeof objCtor == 'function' && objCtor instanceof objCtor &&\n typeof othCtor == 'function' && othCtor instanceof othCtor)) {\n result = false;\n }\n }\n stack['delete'](object);\n stack['delete'](other);\n return result;\n}\n\n/**\n * Creates an array of own enumerable property names and symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction getAllKeys(object) {\n return baseGetAllKeys(object, keys, getSymbols);\n}\n\n/**\n * Gets the data for `map`.\n *\n * @private\n * @param {Object} map The map to query.\n * @param {string} key The reference key.\n * @returns {*} Returns the map data.\n */\nfunction getMapData(map, key) {\n var data = map.__data__;\n return isKeyable(key)\n ? data[typeof key == 'string' ? 'string' : 'hash']\n : data.map;\n}\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\n/**\n * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the raw `toStringTag`.\n */\nfunction getRawTag(value) {\n var isOwn = hasOwnProperty.call(value, symToStringTag),\n tag = value[symToStringTag];\n\n try {\n value[symToStringTag] = undefined;\n var unmasked = true;\n } catch (e) {}\n\n var result = nativeObjectToString.call(value);\n if (unmasked) {\n if (isOwn) {\n value[symToStringTag] = tag;\n } else {\n delete value[symToStringTag];\n }\n }\n return result;\n}\n\n/**\n * Creates an array of the own enumerable symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of symbols.\n */\nvar getSymbols = !nativeGetSymbols ? stubArray : function(object) {\n if (object == null) {\n return [];\n }\n object = Object(object);\n return arrayFilter(nativeGetSymbols(object), function(symbol) {\n return propertyIsEnumerable.call(object, symbol);\n });\n};\n\n/**\n * Gets the `toStringTag` of `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nvar getTag = baseGetTag;\n\n// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.\nif ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||\n (Map && getTag(new Map) != mapTag) ||\n (Promise && getTag(Promise.resolve()) != promiseTag) ||\n (Set && getTag(new Set) != setTag) ||\n (WeakMap && getTag(new WeakMap) != weakMapTag)) {\n getTag = function(value) {\n var result = baseGetTag(value),\n Ctor = result == objectTag ? value.constructor : undefined,\n ctorString = Ctor ? toSource(Ctor) : '';\n\n if (ctorString) {\n switch (ctorString) {\n case dataViewCtorString: return dataViewTag;\n case mapCtorString: return mapTag;\n case promiseCtorString: return promiseTag;\n case setCtorString: return setTag;\n case weakMapCtorString: return weakMapTag;\n }\n }\n return result;\n };\n}\n\n/**\n * Checks if `value` is a valid array-like index.\n *\n * @private\n * @param {*} value The value to check.\n * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.\n * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.\n */\nfunction isIndex(value, length) {\n length = length == null ? MAX_SAFE_INTEGER : length;\n return !!length &&\n (typeof value == 'number' || reIsUint.test(value)) &&\n (value > -1 && value % 1 == 0 && value < length);\n}\n\n/**\n * Checks if `value` is suitable for use as unique object key.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is suitable, else `false`.\n */\nfunction isKeyable(value) {\n var type = typeof value;\n return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')\n ? (value !== '__proto__')\n : (value === null);\n}\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\n/**\n * Checks if `value` is likely a prototype object.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.\n */\nfunction isPrototype(value) {\n var Ctor = value && value.constructor,\n proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;\n\n return value === proto;\n}\n\n/**\n * Converts `value` to a string using `Object.prototype.toString`.\n *\n * @private\n * @param {*} value The value to convert.\n * @returns {string} Returns the converted string.\n */\nfunction objectToString(value) {\n return nativeObjectToString.call(value);\n}\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to convert.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\n/**\n * Performs a\n * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\nfunction eq(value, other) {\n return value === other || (value !== value && other !== other);\n}\n\n/**\n * Checks if `value` is likely an `arguments` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an `arguments` object,\n * else `false`.\n * @example\n *\n * _.isArguments(function() { return arguments; }());\n * // => true\n *\n * _.isArguments([1, 2, 3]);\n * // => false\n */\nvar isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {\n return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&\n !propertyIsEnumerable.call(value, 'callee');\n};\n\n/**\n * Checks if `value` is classified as an `Array` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\n * @example\n *\n * _.isArray([1, 2, 3]);\n * // => true\n *\n * _.isArray(document.body.children);\n * // => false\n *\n * _.isArray('abc');\n * // => false\n *\n * _.isArray(_.noop);\n * // => false\n */\nvar isArray = Array.isArray;\n\n/**\n * Checks if `value` is array-like. A value is considered array-like if it's\n * not a function and has a `value.length` that's an integer greater than or\n * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n * @example\n *\n * _.isArrayLike([1, 2, 3]);\n * // => true\n *\n * _.isArrayLike(document.body.children);\n * // => true\n *\n * _.isArrayLike('abc');\n * // => true\n *\n * _.isArrayLike(_.noop);\n * // => false\n */\nfunction isArrayLike(value) {\n return value != null && isLength(value.length) && !isFunction(value);\n}\n\n/**\n * Checks if `value` is a buffer.\n *\n * @static\n * @memberOf _\n * @since 4.3.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.\n * @example\n *\n * _.isBuffer(new Buffer(2));\n * // => true\n *\n * _.isBuffer(new Uint8Array(2));\n * // => false\n */\nvar isBuffer = nativeIsBuffer || stubFalse;\n\n/**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n *\n * **Note:** This method supports comparing arrays, array buffers, booleans,\n * date objects, error objects, maps, numbers, `Object` objects, regexes,\n * sets, strings, symbols, and typed arrays. `Object` objects are compared\n * by their own, not inherited, enumerable properties. Functions and DOM\n * nodes are compared by strict equality, i.e. `===`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n */\nfunction isEqual(value, other) {\n return baseIsEqual(value, other);\n}\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n if (!isObject(value)) {\n return false;\n }\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 9 which returns 'object' for typed arrays and other constructors.\n var tag = baseGetTag(value);\n return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;\n}\n\n/**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This method is loosely based on\n * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n * @example\n *\n * _.isLength(3);\n * // => true\n *\n * _.isLength(Number.MIN_VALUE);\n * // => false\n *\n * _.isLength(Infinity);\n * // => false\n *\n * _.isLength('3');\n * // => false\n */\nfunction isLength(value) {\n return typeof value == 'number' &&\n value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;\n}\n\n/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return value != null && (type == 'object' || type == 'function');\n}\n\n/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return value != null && typeof value == 'object';\n}\n\n/**\n * Checks if `value` is classified as a typed array.\n *\n * @static\n * @memberOf _\n * @since 3.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.\n * @example\n *\n * _.isTypedArray(new Uint8Array);\n * // => true\n *\n * _.isTypedArray([]);\n * // => false\n */\nvar isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;\n\n/**\n * Creates an array of the own enumerable property names of `object`.\n *\n * **Note:** Non-object values are coerced to objects. See the\n * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)\n * for more details.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n * @example\n *\n * function Foo() {\n * this.a = 1;\n * this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.keys(new Foo);\n * // => ['a', 'b'] (iteration order is not guaranteed)\n *\n * _.keys('hi');\n * // => ['0', '1']\n */\nfunction keys(object) {\n return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);\n}\n\n/**\n * This method returns a new empty array.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {Array} Returns the new empty array.\n * @example\n *\n * var arrays = _.times(2, _.stubArray);\n *\n * console.log(arrays);\n * // => [[], []]\n *\n * console.log(arrays[0] === arrays[1]);\n * // => false\n */\nfunction stubArray() {\n return [];\n}\n\n/**\n * This method returns `false`.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {boolean} Returns `false`.\n * @example\n *\n * _.times(2, _.stubFalse);\n * // => [false, false]\n */\nfunction stubFalse() {\n return false;\n}\n\nmodule.exports = isEqual;\n\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../../ckeditor5-mini-inspector/node_modules/webpack/buildin/global.js */ \"./node_modules/webpack/buildin/global.js\"), __webpack_require__(/*! ./../../../ckeditor5-mini-inspector/node_modules/webpack/buildin/module.js */ \"./node_modules/webpack/buildin/module.js\")(module)))\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/lodash.isequal/index.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/object-assign/index.js": +/*!******************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/object-assign/index.js ***! + \******************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/object-assign/index.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/prop-types/checkPropTypes.js": +/*!************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/prop-types/checkPropTypes.js ***! + \************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar printWarning = function() {};\n\nif (true) {\n var ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"../ckeditor5-inspector/node_modules/prop-types/lib/ReactPropTypesSecret.js\");\n var loggedTypeFailures = {};\n var has = Function.call.bind(Object.prototype.hasOwnProperty);\n\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\n/**\n * Assert that the values match with the type specs.\n * Error messages are memorized and will only be shown once.\n *\n * @param {object} typeSpecs Map of name to a ReactPropType\n * @param {object} values Runtime values that need to be type-checked\n * @param {string} location e.g. \"prop\", \"context\", \"child context\"\n * @param {string} componentName Name of the component for error messages.\n * @param {?Function} getStack Returns the component stack.\n * @private\n */\nfunction checkPropTypes(typeSpecs, values, location, componentName, getStack) {\n if (true) {\n for (var typeSpecName in typeSpecs) {\n if (has(typeSpecs, typeSpecName)) {\n var error;\n // Prop type validation may throw. In case they do, we don't want to\n // fail the render phase where it didn't fail before. So we log it.\n // After these have been cleaned up, we'll let them throw.\n try {\n // This is intentionally an invariant that gets caught. It's the same\n // behavior as without this statement except with a better message.\n if (typeof typeSpecs[typeSpecName] !== 'function') {\n var err = Error(\n (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +\n 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.'\n );\n err.name = 'Invariant Violation';\n throw err;\n }\n error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);\n } catch (ex) {\n error = ex;\n }\n if (error && !(error instanceof Error)) {\n printWarning(\n (componentName || 'React class') + ': type specification of ' +\n location + ' `' + typeSpecName + '` is invalid; the type checker ' +\n 'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +\n 'You may have forgotten to pass an argument to the type checker ' +\n 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +\n 'shape all require an argument).'\n );\n }\n if (error instanceof Error && !(error.message in loggedTypeFailures)) {\n // Only monitor this failure once because there tends to be a lot of the\n // same error.\n loggedTypeFailures[error.message] = true;\n\n var stack = getStack ? getStack() : '';\n\n printWarning(\n 'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')\n );\n }\n }\n }\n }\n}\n\n/**\n * Resets warning cache when testing.\n *\n * @private\n */\ncheckPropTypes.resetWarningCache = function() {\n if (true) {\n loggedTypeFailures = {};\n }\n}\n\nmodule.exports = checkPropTypes;\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/prop-types/checkPropTypes.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/prop-types/lib/ReactPropTypesSecret.js": +/*!**********************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/prop-types/lib/ReactPropTypesSecret.js ***! + \**********************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/prop-types/lib/ReactPropTypesSecret.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/react/cjs/react.development.js": +/*!**************************************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/react/cjs/react.development.js ***! + \**************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/** @license React v16.14.0\n * react.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\n\n\nif (true) {\n (function() {\n'use strict';\n\nvar _assign = __webpack_require__(/*! object-assign */ \"../ckeditor5-inspector/node_modules/object-assign/index.js\");\nvar checkPropTypes = __webpack_require__(/*! prop-types/checkPropTypes */ \"../ckeditor5-inspector/node_modules/prop-types/checkPropTypes.js\");\n\nvar ReactVersion = '16.14.0';\n\n// The Symbol used to tag the ReactElement-like types. If there is no native Symbol\n// nor polyfill, then a plain number is used for performance.\nvar hasSymbol = typeof Symbol === 'function' && Symbol.for;\nvar REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;\nvar REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;\nvar REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;\nvar REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;\nvar REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;\nvar REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;\nvar REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace; // TODO: We don't use AsyncMode or ConcurrentMode anymore. They were temporary\nvar REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;\nvar REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;\nvar REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;\nvar REACT_SUSPENSE_LIST_TYPE = hasSymbol ? Symbol.for('react.suspense_list') : 0xead8;\nvar REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;\nvar REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;\nvar REACT_BLOCK_TYPE = hasSymbol ? Symbol.for('react.block') : 0xead9;\nvar REACT_FUNDAMENTAL_TYPE = hasSymbol ? Symbol.for('react.fundamental') : 0xead5;\nvar REACT_RESPONDER_TYPE = hasSymbol ? Symbol.for('react.responder') : 0xead6;\nvar REACT_SCOPE_TYPE = hasSymbol ? Symbol.for('react.scope') : 0xead7;\nvar MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\nvar FAUX_ITERATOR_SYMBOL = '@@iterator';\nfunction getIteratorFn(maybeIterable) {\n if (maybeIterable === null || typeof maybeIterable !== 'object') {\n return null;\n }\n\n var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];\n\n if (typeof maybeIterator === 'function') {\n return maybeIterator;\n }\n\n return null;\n}\n\n/**\n * Keeps track of the current dispatcher.\n */\nvar ReactCurrentDispatcher = {\n /**\n * @internal\n * @type {ReactComponent}\n */\n current: null\n};\n\n/**\n * Keeps track of the current batch's configuration such as how long an update\n * should suspend for if it needs to.\n */\nvar ReactCurrentBatchConfig = {\n suspense: null\n};\n\n/**\n * Keeps track of the current owner.\n *\n * The current owner is the component who should own any components that are\n * currently being constructed.\n */\nvar ReactCurrentOwner = {\n /**\n * @internal\n * @type {ReactComponent}\n */\n current: null\n};\n\nvar BEFORE_SLASH_RE = /^(.*)[\\\\\\/]/;\nfunction describeComponentFrame (name, source, ownerName) {\n var sourceInfo = '';\n\n if (source) {\n var path = source.fileName;\n var fileName = path.replace(BEFORE_SLASH_RE, '');\n\n {\n // In DEV, include code for a common special case:\n // prefer \"folder/index.js\" instead of just \"index.js\".\n if (/^index\\./.test(fileName)) {\n var match = path.match(BEFORE_SLASH_RE);\n\n if (match) {\n var pathBeforeSlash = match[1];\n\n if (pathBeforeSlash) {\n var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');\n fileName = folderName + '/' + fileName;\n }\n }\n }\n }\n\n sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';\n } else if (ownerName) {\n sourceInfo = ' (created by ' + ownerName + ')';\n }\n\n return '\\n in ' + (name || 'Unknown') + sourceInfo;\n}\n\nvar Resolved = 1;\nfunction refineResolvedLazyComponent(lazyComponent) {\n return lazyComponent._status === Resolved ? lazyComponent._result : null;\n}\n\nfunction getWrappedName(outerType, innerType, wrapperName) {\n var functionName = innerType.displayName || innerType.name || '';\n return outerType.displayName || (functionName !== '' ? wrapperName + \"(\" + functionName + \")\" : wrapperName);\n}\n\nfunction getComponentName(type) {\n if (type == null) {\n // Host root, text node or just invalid type.\n return null;\n }\n\n {\n if (typeof type.tag === 'number') {\n error('Received an unexpected object in getComponentName(). ' + 'This is likely a bug in React. Please file an issue.');\n }\n }\n\n if (typeof type === 'function') {\n return type.displayName || type.name || null;\n }\n\n if (typeof type === 'string') {\n return type;\n }\n\n switch (type) {\n case REACT_FRAGMENT_TYPE:\n return 'Fragment';\n\n case REACT_PORTAL_TYPE:\n return 'Portal';\n\n case REACT_PROFILER_TYPE:\n return \"Profiler\";\n\n case REACT_STRICT_MODE_TYPE:\n return 'StrictMode';\n\n case REACT_SUSPENSE_TYPE:\n return 'Suspense';\n\n case REACT_SUSPENSE_LIST_TYPE:\n return 'SuspenseList';\n }\n\n if (typeof type === 'object') {\n switch (type.$$typeof) {\n case REACT_CONTEXT_TYPE:\n return 'Context.Consumer';\n\n case REACT_PROVIDER_TYPE:\n return 'Context.Provider';\n\n case REACT_FORWARD_REF_TYPE:\n return getWrappedName(type, type.render, 'ForwardRef');\n\n case REACT_MEMO_TYPE:\n return getComponentName(type.type);\n\n case REACT_BLOCK_TYPE:\n return getComponentName(type.render);\n\n case REACT_LAZY_TYPE:\n {\n var thenable = type;\n var resolvedThenable = refineResolvedLazyComponent(thenable);\n\n if (resolvedThenable) {\n return getComponentName(resolvedThenable);\n }\n\n break;\n }\n }\n }\n\n return null;\n}\n\nvar ReactDebugCurrentFrame = {};\nvar currentlyValidatingElement = null;\nfunction setCurrentlyValidatingElement(element) {\n {\n currentlyValidatingElement = element;\n }\n}\n\n{\n // Stack implementation injected by the current renderer.\n ReactDebugCurrentFrame.getCurrentStack = null;\n\n ReactDebugCurrentFrame.getStackAddendum = function () {\n var stack = ''; // Add an extra top frame while an element is being validated\n\n if (currentlyValidatingElement) {\n var name = getComponentName(currentlyValidatingElement.type);\n var owner = currentlyValidatingElement._owner;\n stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner.type));\n } // Delegate to the injected renderer-specific implementation\n\n\n var impl = ReactDebugCurrentFrame.getCurrentStack;\n\n if (impl) {\n stack += impl() || '';\n }\n\n return stack;\n };\n}\n\n/**\n * Used by act() to track whether you're inside an act() scope.\n */\nvar IsSomeRendererActing = {\n current: false\n};\n\nvar ReactSharedInternals = {\n ReactCurrentDispatcher: ReactCurrentDispatcher,\n ReactCurrentBatchConfig: ReactCurrentBatchConfig,\n ReactCurrentOwner: ReactCurrentOwner,\n IsSomeRendererActing: IsSomeRendererActing,\n // Used by renderers to avoid bundling object-assign twice in UMD bundles:\n assign: _assign\n};\n\n{\n _assign(ReactSharedInternals, {\n // These should not be included in production.\n ReactDebugCurrentFrame: ReactDebugCurrentFrame,\n // Shim for React DOM 16.0.0 which still destructured (but not used) this.\n // TODO: remove in React 17.0.\n ReactComponentTreeHook: {}\n });\n}\n\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n printWarning('warn', format, args);\n }\n}\nfunction error(format) {\n {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var hasExistingStack = args.length > 0 && typeof args[args.length - 1] === 'string' && args[args.length - 1].indexOf('\\n in') === 0;\n\n if (!hasExistingStack) {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n }\n }\n\n var argsWithFormat = args.map(function (item) {\n return '' + item;\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n var argIndex = 0;\n var message = 'Warning: ' + format.replace(/%s/g, function () {\n return args[argIndex++];\n });\n throw new Error(message);\n } catch (x) {}\n }\n}\n\nvar didWarnStateUpdateForUnmountedComponent = {};\n\nfunction warnNoop(publicInstance, callerName) {\n {\n var _constructor = publicInstance.constructor;\n var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';\n var warningKey = componentName + \".\" + callerName;\n\n if (didWarnStateUpdateForUnmountedComponent[warningKey]) {\n return;\n }\n\n error(\"Can't call %s on a component that is not yet mounted. \" + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);\n\n didWarnStateUpdateForUnmountedComponent[warningKey] = true;\n }\n}\n/**\n * This is the abstract API for an update queue.\n */\n\n\nvar ReactNoopUpdateQueue = {\n /**\n * Checks whether or not this composite component is mounted.\n * @param {ReactClass} publicInstance The instance we want to test.\n * @return {boolean} True if mounted, false otherwise.\n * @protected\n * @final\n */\n isMounted: function (publicInstance) {\n return false;\n },\n\n /**\n * Forces an update. This should only be invoked when it is known with\n * certainty that we are **not** in a DOM transaction.\n *\n * You may want to call this when you know that some deeper aspect of the\n * component's state has changed but `setState` was not called.\n *\n * This will not invoke `shouldComponentUpdate`, but it will invoke\n * `componentWillUpdate` and `componentDidUpdate`.\n *\n * @param {ReactClass} publicInstance The instance that should rerender.\n * @param {?function} callback Called after component is updated.\n * @param {?string} callerName name of the calling function in the public API.\n * @internal\n */\n enqueueForceUpdate: function (publicInstance, callback, callerName) {\n warnNoop(publicInstance, 'forceUpdate');\n },\n\n /**\n * Replaces all of the state. Always use this or `setState` to mutate state.\n * You should treat `this.state` as immutable.\n *\n * There is no guarantee that `this.state` will be immediately updated, so\n * accessing `this.state` after calling this method may return the old value.\n *\n * @param {ReactClass} publicInstance The instance that should rerender.\n * @param {object} completeState Next state.\n * @param {?function} callback Called after component is updated.\n * @param {?string} callerName name of the calling function in the public API.\n * @internal\n */\n enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {\n warnNoop(publicInstance, 'replaceState');\n },\n\n /**\n * Sets a subset of the state. This only exists because _pendingState is\n * internal. This provides a merging strategy that is not available to deep\n * properties which is confusing. TODO: Expose pendingState or don't use it\n * during the merge.\n *\n * @param {ReactClass} publicInstance The instance that should rerender.\n * @param {object} partialState Next partial state to be merged with state.\n * @param {?function} callback Called after component is updated.\n * @param {?string} Name of the calling function in the public API.\n * @internal\n */\n enqueueSetState: function (publicInstance, partialState, callback, callerName) {\n warnNoop(publicInstance, 'setState');\n }\n};\n\nvar emptyObject = {};\n\n{\n Object.freeze(emptyObject);\n}\n/**\n * Base class helpers for the updating state of a component.\n */\n\n\nfunction Component(props, context, updater) {\n this.props = props;\n this.context = context; // If a component has string refs, we will assign a different object later.\n\n this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the\n // renderer.\n\n this.updater = updater || ReactNoopUpdateQueue;\n}\n\nComponent.prototype.isReactComponent = {};\n/**\n * Sets a subset of the state. Always use this to mutate\n * state. You should treat `this.state` as immutable.\n *\n * There is no guarantee that `this.state` will be immediately updated, so\n * accessing `this.state` after calling this method may return the old value.\n *\n * There is no guarantee that calls to `setState` will run synchronously,\n * as they may eventually be batched together. You can provide an optional\n * callback that will be executed when the call to setState is actually\n * completed.\n *\n * When a function is provided to setState, it will be called at some point in\n * the future (not synchronously). It will be called with the up to date\n * component arguments (state, props, context). These values can be different\n * from this.* because your function may be called after receiveProps but before\n * shouldComponentUpdate, and this new state, props, and context will not yet be\n * assigned to this.\n *\n * @param {object|function} partialState Next partial state or function to\n * produce next partial state to be merged with current state.\n * @param {?function} callback Called after state is updated.\n * @final\n * @protected\n */\n\nComponent.prototype.setState = function (partialState, callback) {\n if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {\n {\n throw Error( \"setState(...): takes an object of state variables to update or a function which returns an object of state variables.\" );\n }\n }\n\n this.updater.enqueueSetState(this, partialState, callback, 'setState');\n};\n/**\n * Forces an update. This should only be invoked when it is known with\n * certainty that we are **not** in a DOM transaction.\n *\n * You may want to call this when you know that some deeper aspect of the\n * component's state has changed but `setState` was not called.\n *\n * This will not invoke `shouldComponentUpdate`, but it will invoke\n * `componentWillUpdate` and `componentDidUpdate`.\n *\n * @param {?function} callback Called after update is complete.\n * @final\n * @protected\n */\n\n\nComponent.prototype.forceUpdate = function (callback) {\n this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');\n};\n/**\n * Deprecated APIs. These APIs used to exist on classic React classes but since\n * we would like to deprecate them, we're not going to move them over to this\n * modern base class. Instead, we define a getter that warns if it's accessed.\n */\n\n\n{\n var deprecatedAPIs = {\n isMounted: ['isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.'],\n replaceState: ['replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).']\n };\n\n var defineDeprecationWarning = function (methodName, info) {\n Object.defineProperty(Component.prototype, methodName, {\n get: function () {\n warn('%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1]);\n\n return undefined;\n }\n });\n };\n\n for (var fnName in deprecatedAPIs) {\n if (deprecatedAPIs.hasOwnProperty(fnName)) {\n defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);\n }\n }\n}\n\nfunction ComponentDummy() {}\n\nComponentDummy.prototype = Component.prototype;\n/**\n * Convenience component with default shallow equality check for sCU.\n */\n\nfunction PureComponent(props, context, updater) {\n this.props = props;\n this.context = context; // If a component has string refs, we will assign a different object later.\n\n this.refs = emptyObject;\n this.updater = updater || ReactNoopUpdateQueue;\n}\n\nvar pureComponentPrototype = PureComponent.prototype = new ComponentDummy();\npureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods.\n\n_assign(pureComponentPrototype, Component.prototype);\n\npureComponentPrototype.isPureReactComponent = true;\n\n// an immutable object with a single mutable value\nfunction createRef() {\n var refObject = {\n current: null\n };\n\n {\n Object.seal(refObject);\n }\n\n return refObject;\n}\n\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar RESERVED_PROPS = {\n key: true,\n ref: true,\n __self: true,\n __source: true\n};\nvar specialPropKeyWarningShown, specialPropRefWarningShown, didWarnAboutStringRefs;\n\n{\n didWarnAboutStringRefs = {};\n}\n\nfunction hasValidRef(config) {\n {\n if (hasOwnProperty.call(config, 'ref')) {\n var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;\n\n if (getter && getter.isReactWarning) {\n return false;\n }\n }\n }\n\n return config.ref !== undefined;\n}\n\nfunction hasValidKey(config) {\n {\n if (hasOwnProperty.call(config, 'key')) {\n var getter = Object.getOwnPropertyDescriptor(config, 'key').get;\n\n if (getter && getter.isReactWarning) {\n return false;\n }\n }\n }\n\n return config.key !== undefined;\n}\n\nfunction defineKeyPropWarningGetter(props, displayName) {\n var warnAboutAccessingKey = function () {\n {\n if (!specialPropKeyWarningShown) {\n specialPropKeyWarningShown = true;\n\n error('%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);\n }\n }\n };\n\n warnAboutAccessingKey.isReactWarning = true;\n Object.defineProperty(props, 'key', {\n get: warnAboutAccessingKey,\n configurable: true\n });\n}\n\nfunction defineRefPropWarningGetter(props, displayName) {\n var warnAboutAccessingRef = function () {\n {\n if (!specialPropRefWarningShown) {\n specialPropRefWarningShown = true;\n\n error('%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);\n }\n }\n };\n\n warnAboutAccessingRef.isReactWarning = true;\n Object.defineProperty(props, 'ref', {\n get: warnAboutAccessingRef,\n configurable: true\n });\n}\n\nfunction warnIfStringRefCannotBeAutoConverted(config) {\n {\n if (typeof config.ref === 'string' && ReactCurrentOwner.current && config.__self && ReactCurrentOwner.current.stateNode !== config.__self) {\n var componentName = getComponentName(ReactCurrentOwner.current.type);\n\n if (!didWarnAboutStringRefs[componentName]) {\n error('Component \"%s\" contains the string ref \"%s\". ' + 'Support for string refs will be removed in a future major release. ' + 'This case cannot be automatically converted to an arrow function. ' + 'We ask you to manually fix this case by using useRef() or createRef() instead. ' + 'Learn more about using refs safely here: ' + 'https://fb.me/react-strict-mode-string-ref', getComponentName(ReactCurrentOwner.current.type), config.ref);\n\n didWarnAboutStringRefs[componentName] = true;\n }\n }\n }\n}\n/**\n * Factory method to create a new React element. This no longer adheres to\n * the class pattern, so do not use new to call it. Also, instanceof check\n * will not work. Instead test $$typeof field against Symbol.for('react.element') to check\n * if something is a React Element.\n *\n * @param {*} type\n * @param {*} props\n * @param {*} key\n * @param {string|object} ref\n * @param {*} owner\n * @param {*} self A *temporary* helper to detect places where `this` is\n * different from the `owner` when React.createElement is called, so that we\n * can warn. We want to get rid of owner and replace string `ref`s with arrow\n * functions, and as long as `this` and owner are the same, there will be no\n * change in behavior.\n * @param {*} source An annotation object (added by a transpiler or otherwise)\n * indicating filename, line number, and/or other information.\n * @internal\n */\n\n\nvar ReactElement = function (type, key, ref, self, source, owner, props) {\n var element = {\n // This tag allows us to uniquely identify this as a React Element\n $$typeof: REACT_ELEMENT_TYPE,\n // Built-in properties that belong on the element\n type: type,\n key: key,\n ref: ref,\n props: props,\n // Record the component responsible for creating this element.\n _owner: owner\n };\n\n {\n // The validation flag is currently mutative. We put it on\n // an external backing store so that we can freeze the whole object.\n // This can be replaced with a WeakMap once they are implemented in\n // commonly used development environments.\n element._store = {}; // To make comparing ReactElements easier for testing purposes, we make\n // the validation flag non-enumerable (where possible, which should\n // include every environment we run tests in), so the test framework\n // ignores it.\n\n Object.defineProperty(element._store, 'validated', {\n configurable: false,\n enumerable: false,\n writable: true,\n value: false\n }); // self and source are DEV only properties.\n\n Object.defineProperty(element, '_self', {\n configurable: false,\n enumerable: false,\n writable: false,\n value: self\n }); // Two elements created in two different places should be considered\n // equal for testing purposes and therefore we hide it from enumeration.\n\n Object.defineProperty(element, '_source', {\n configurable: false,\n enumerable: false,\n writable: false,\n value: source\n });\n\n if (Object.freeze) {\n Object.freeze(element.props);\n Object.freeze(element);\n }\n }\n\n return element;\n};\n/**\n * Create and return a new ReactElement of the given type.\n * See https://reactjs.org/docs/react-api.html#createelement\n */\n\nfunction createElement(type, config, children) {\n var propName; // Reserved names are extracted\n\n var props = {};\n var key = null;\n var ref = null;\n var self = null;\n var source = null;\n\n if (config != null) {\n if (hasValidRef(config)) {\n ref = config.ref;\n\n {\n warnIfStringRefCannotBeAutoConverted(config);\n }\n }\n\n if (hasValidKey(config)) {\n key = '' + config.key;\n }\n\n self = config.__self === undefined ? null : config.__self;\n source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object\n\n for (propName in config) {\n if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {\n props[propName] = config[propName];\n }\n }\n } // Children can be more than one argument, and those are transferred onto\n // the newly allocated props object.\n\n\n var childrenLength = arguments.length - 2;\n\n if (childrenLength === 1) {\n props.children = children;\n } else if (childrenLength > 1) {\n var childArray = Array(childrenLength);\n\n for (var i = 0; i < childrenLength; i++) {\n childArray[i] = arguments[i + 2];\n }\n\n {\n if (Object.freeze) {\n Object.freeze(childArray);\n }\n }\n\n props.children = childArray;\n } // Resolve default props\n\n\n if (type && type.defaultProps) {\n var defaultProps = type.defaultProps;\n\n for (propName in defaultProps) {\n if (props[propName] === undefined) {\n props[propName] = defaultProps[propName];\n }\n }\n }\n\n {\n if (key || ref) {\n var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;\n\n if (key) {\n defineKeyPropWarningGetter(props, displayName);\n }\n\n if (ref) {\n defineRefPropWarningGetter(props, displayName);\n }\n }\n }\n\n return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);\n}\nfunction cloneAndReplaceKey(oldElement, newKey) {\n var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props);\n return newElement;\n}\n/**\n * Clone and return a new ReactElement using element as the starting point.\n * See https://reactjs.org/docs/react-api.html#cloneelement\n */\n\nfunction cloneElement(element, config, children) {\n if (!!(element === null || element === undefined)) {\n {\n throw Error( \"React.cloneElement(...): The argument must be a React element, but you passed \" + element + \".\" );\n }\n }\n\n var propName; // Original props are copied\n\n var props = _assign({}, element.props); // Reserved names are extracted\n\n\n var key = element.key;\n var ref = element.ref; // Self is preserved since the owner is preserved.\n\n var self = element._self; // Source is preserved since cloneElement is unlikely to be targeted by a\n // transpiler, and the original source is probably a better indicator of the\n // true owner.\n\n var source = element._source; // Owner will be preserved, unless ref is overridden\n\n var owner = element._owner;\n\n if (config != null) {\n if (hasValidRef(config)) {\n // Silently steal the ref from the parent.\n ref = config.ref;\n owner = ReactCurrentOwner.current;\n }\n\n if (hasValidKey(config)) {\n key = '' + config.key;\n } // Remaining properties override existing props\n\n\n var defaultProps;\n\n if (element.type && element.type.defaultProps) {\n defaultProps = element.type.defaultProps;\n }\n\n for (propName in config) {\n if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {\n if (config[propName] === undefined && defaultProps !== undefined) {\n // Resolve default props\n props[propName] = defaultProps[propName];\n } else {\n props[propName] = config[propName];\n }\n }\n }\n } // Children can be more than one argument, and those are transferred onto\n // the newly allocated props object.\n\n\n var childrenLength = arguments.length - 2;\n\n if (childrenLength === 1) {\n props.children = children;\n } else if (childrenLength > 1) {\n var childArray = Array(childrenLength);\n\n for (var i = 0; i < childrenLength; i++) {\n childArray[i] = arguments[i + 2];\n }\n\n props.children = childArray;\n }\n\n return ReactElement(element.type, key, ref, self, source, owner, props);\n}\n/**\n * Verifies the object is a ReactElement.\n * See https://reactjs.org/docs/react-api.html#isvalidelement\n * @param {?object} object\n * @return {boolean} True if `object` is a ReactElement.\n * @final\n */\n\nfunction isValidElement(object) {\n return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;\n}\n\nvar SEPARATOR = '.';\nvar SUBSEPARATOR = ':';\n/**\n * Escape and wrap key so it is safe to use as a reactid\n *\n * @param {string} key to be escaped.\n * @return {string} the escaped key.\n */\n\nfunction escape(key) {\n var escapeRegex = /[=:]/g;\n var escaperLookup = {\n '=': '=0',\n ':': '=2'\n };\n var escapedString = ('' + key).replace(escapeRegex, function (match) {\n return escaperLookup[match];\n });\n return '$' + escapedString;\n}\n/**\n * TODO: Test that a single child and an array with one item have the same key\n * pattern.\n */\n\n\nvar didWarnAboutMaps = false;\nvar userProvidedKeyEscapeRegex = /\\/+/g;\n\nfunction escapeUserProvidedKey(text) {\n return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');\n}\n\nvar POOL_SIZE = 10;\nvar traverseContextPool = [];\n\nfunction getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) {\n if (traverseContextPool.length) {\n var traverseContext = traverseContextPool.pop();\n traverseContext.result = mapResult;\n traverseContext.keyPrefix = keyPrefix;\n traverseContext.func = mapFunction;\n traverseContext.context = mapContext;\n traverseContext.count = 0;\n return traverseContext;\n } else {\n return {\n result: mapResult,\n keyPrefix: keyPrefix,\n func: mapFunction,\n context: mapContext,\n count: 0\n };\n }\n}\n\nfunction releaseTraverseContext(traverseContext) {\n traverseContext.result = null;\n traverseContext.keyPrefix = null;\n traverseContext.func = null;\n traverseContext.context = null;\n traverseContext.count = 0;\n\n if (traverseContextPool.length < POOL_SIZE) {\n traverseContextPool.push(traverseContext);\n }\n}\n/**\n * @param {?*} children Children tree container.\n * @param {!string} nameSoFar Name of the key path so far.\n * @param {!function} callback Callback to invoke with each child found.\n * @param {?*} traverseContext Used to pass information throughout the traversal\n * process.\n * @return {!number} The number of children in this subtree.\n */\n\n\nfunction traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {\n var type = typeof children;\n\n if (type === 'undefined' || type === 'boolean') {\n // All of the above are perceived as null.\n children = null;\n }\n\n var invokeCallback = false;\n\n if (children === null) {\n invokeCallback = true;\n } else {\n switch (type) {\n case 'string':\n case 'number':\n invokeCallback = true;\n break;\n\n case 'object':\n switch (children.$$typeof) {\n case REACT_ELEMENT_TYPE:\n case REACT_PORTAL_TYPE:\n invokeCallback = true;\n }\n\n }\n }\n\n if (invokeCallback) {\n callback(traverseContext, children, // If it's the only child, treat the name as if it was wrapped in an array\n // so that it's consistent if the number of children grows.\n nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);\n return 1;\n }\n\n var child;\n var nextName;\n var subtreeCount = 0; // Count of children found in the current subtree.\n\n var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;\n\n if (Array.isArray(children)) {\n for (var i = 0; i < children.length; i++) {\n child = children[i];\n nextName = nextNamePrefix + getComponentKey(child, i);\n subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);\n }\n } else {\n var iteratorFn = getIteratorFn(children);\n\n if (typeof iteratorFn === 'function') {\n\n {\n // Warn about using Maps as children\n if (iteratorFn === children.entries) {\n if (!didWarnAboutMaps) {\n warn('Using Maps as children is deprecated and will be removed in ' + 'a future major release. Consider converting children to ' + 'an array of keyed ReactElements instead.');\n }\n\n didWarnAboutMaps = true;\n }\n }\n\n var iterator = iteratorFn.call(children);\n var step;\n var ii = 0;\n\n while (!(step = iterator.next()).done) {\n child = step.value;\n nextName = nextNamePrefix + getComponentKey(child, ii++);\n subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);\n }\n } else if (type === 'object') {\n var addendum = '';\n\n {\n addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum();\n }\n\n var childrenString = '' + children;\n\n {\n {\n throw Error( \"Objects are not valid as a React child (found: \" + (childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString) + \").\" + addendum );\n }\n }\n }\n }\n\n return subtreeCount;\n}\n/**\n * Traverses children that are typically specified as `props.children`, but\n * might also be specified through attributes:\n *\n * - `traverseAllChildren(this.props.children, ...)`\n * - `traverseAllChildren(this.props.leftPanelChildren, ...)`\n *\n * The `traverseContext` is an optional argument that is passed through the\n * entire traversal. It can be used to store accumulations or anything else that\n * the callback might find relevant.\n *\n * @param {?*} children Children tree object.\n * @param {!function} callback To invoke upon traversing each child.\n * @param {?*} traverseContext Context for traversal.\n * @return {!number} The number of children in this subtree.\n */\n\n\nfunction traverseAllChildren(children, callback, traverseContext) {\n if (children == null) {\n return 0;\n }\n\n return traverseAllChildrenImpl(children, '', callback, traverseContext);\n}\n/**\n * Generate a key string that identifies a component within a set.\n *\n * @param {*} component A component that could contain a manual key.\n * @param {number} index Index that is used if a manual key is not provided.\n * @return {string}\n */\n\n\nfunction getComponentKey(component, index) {\n // Do some typechecking here since we call this blindly. We want to ensure\n // that we don't block potential future ES APIs.\n if (typeof component === 'object' && component !== null && component.key != null) {\n // Explicit key\n return escape(component.key);\n } // Implicit key determined by the index in the set\n\n\n return index.toString(36);\n}\n\nfunction forEachSingleChild(bookKeeping, child, name) {\n var func = bookKeeping.func,\n context = bookKeeping.context;\n func.call(context, child, bookKeeping.count++);\n}\n/**\n * Iterates through children that are typically specified as `props.children`.\n *\n * See https://reactjs.org/docs/react-api.html#reactchildrenforeach\n *\n * The provided forEachFunc(child, index) will be called for each\n * leaf child.\n *\n * @param {?*} children Children tree container.\n * @param {function(*, int)} forEachFunc\n * @param {*} forEachContext Context for forEachContext.\n */\n\n\nfunction forEachChildren(children, forEachFunc, forEachContext) {\n if (children == null) {\n return children;\n }\n\n var traverseContext = getPooledTraverseContext(null, null, forEachFunc, forEachContext);\n traverseAllChildren(children, forEachSingleChild, traverseContext);\n releaseTraverseContext(traverseContext);\n}\n\nfunction mapSingleChildIntoContext(bookKeeping, child, childKey) {\n var result = bookKeeping.result,\n keyPrefix = bookKeeping.keyPrefix,\n func = bookKeeping.func,\n context = bookKeeping.context;\n var mappedChild = func.call(context, child, bookKeeping.count++);\n\n if (Array.isArray(mappedChild)) {\n mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, function (c) {\n return c;\n });\n } else if (mappedChild != null) {\n if (isValidElement(mappedChild)) {\n mappedChild = cloneAndReplaceKey(mappedChild, // Keep both the (mapped) and old keys if they differ, just as\n // traverseAllChildren used to do for objects as children\n keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey);\n }\n\n result.push(mappedChild);\n }\n}\n\nfunction mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {\n var escapedPrefix = '';\n\n if (prefix != null) {\n escapedPrefix = escapeUserProvidedKey(prefix) + '/';\n }\n\n var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);\n traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);\n releaseTraverseContext(traverseContext);\n}\n/**\n * Maps children that are typically specified as `props.children`.\n *\n * See https://reactjs.org/docs/react-api.html#reactchildrenmap\n *\n * The provided mapFunction(child, key, index) will be called for each\n * leaf child.\n *\n * @param {?*} children Children tree container.\n * @param {function(*, int)} func The map function.\n * @param {*} context Context for mapFunction.\n * @return {object} Object containing the ordered map of results.\n */\n\n\nfunction mapChildren(children, func, context) {\n if (children == null) {\n return children;\n }\n\n var result = [];\n mapIntoWithKeyPrefixInternal(children, result, null, func, context);\n return result;\n}\n/**\n * Count the number of children that are typically specified as\n * `props.children`.\n *\n * See https://reactjs.org/docs/react-api.html#reactchildrencount\n *\n * @param {?*} children Children tree container.\n * @return {number} The number of children.\n */\n\n\nfunction countChildren(children) {\n return traverseAllChildren(children, function () {\n return null;\n }, null);\n}\n/**\n * Flatten a children object (typically specified as `props.children`) and\n * return an array with appropriately re-keyed children.\n *\n * See https://reactjs.org/docs/react-api.html#reactchildrentoarray\n */\n\n\nfunction toArray(children) {\n var result = [];\n mapIntoWithKeyPrefixInternal(children, result, null, function (child) {\n return child;\n });\n return result;\n}\n/**\n * Returns the first child in a collection of children and verifies that there\n * is only one child in the collection.\n *\n * See https://reactjs.org/docs/react-api.html#reactchildrenonly\n *\n * The current implementation of this function assumes that a single child gets\n * passed without a wrapper, but the purpose of this helper function is to\n * abstract away the particular structure of children.\n *\n * @param {?object} children Child collection structure.\n * @return {ReactElement} The first and only `ReactElement` contained in the\n * structure.\n */\n\n\nfunction onlyChild(children) {\n if (!isValidElement(children)) {\n {\n throw Error( \"React.Children.only expected to receive a single React element child.\" );\n }\n }\n\n return children;\n}\n\nfunction createContext(defaultValue, calculateChangedBits) {\n if (calculateChangedBits === undefined) {\n calculateChangedBits = null;\n } else {\n {\n if (calculateChangedBits !== null && typeof calculateChangedBits !== 'function') {\n error('createContext: Expected the optional second argument to be a ' + 'function. Instead received: %s', calculateChangedBits);\n }\n }\n }\n\n var context = {\n $$typeof: REACT_CONTEXT_TYPE,\n _calculateChangedBits: calculateChangedBits,\n // As a workaround to support multiple concurrent renderers, we categorize\n // some renderers as primary and others as secondary. We only expect\n // there to be two concurrent renderers at most: React Native (primary) and\n // Fabric (secondary); React DOM (primary) and React ART (secondary).\n // Secondary renderers store their context values on separate fields.\n _currentValue: defaultValue,\n _currentValue2: defaultValue,\n // Used to track how many concurrent renderers this context currently\n // supports within in a single renderer. Such as parallel server rendering.\n _threadCount: 0,\n // These are circular\n Provider: null,\n Consumer: null\n };\n context.Provider = {\n $$typeof: REACT_PROVIDER_TYPE,\n _context: context\n };\n var hasWarnedAboutUsingNestedContextConsumers = false;\n var hasWarnedAboutUsingConsumerProvider = false;\n\n {\n // A separate object, but proxies back to the original context object for\n // backwards compatibility. It has a different $$typeof, so we can properly\n // warn for the incorrect usage of Context as a Consumer.\n var Consumer = {\n $$typeof: REACT_CONTEXT_TYPE,\n _context: context,\n _calculateChangedBits: context._calculateChangedBits\n }; // $FlowFixMe: Flow complains about not setting a value, which is intentional here\n\n Object.defineProperties(Consumer, {\n Provider: {\n get: function () {\n if (!hasWarnedAboutUsingConsumerProvider) {\n hasWarnedAboutUsingConsumerProvider = true;\n\n error('Rendering is not supported and will be removed in ' + 'a future major release. Did you mean to render instead?');\n }\n\n return context.Provider;\n },\n set: function (_Provider) {\n context.Provider = _Provider;\n }\n },\n _currentValue: {\n get: function () {\n return context._currentValue;\n },\n set: function (_currentValue) {\n context._currentValue = _currentValue;\n }\n },\n _currentValue2: {\n get: function () {\n return context._currentValue2;\n },\n set: function (_currentValue2) {\n context._currentValue2 = _currentValue2;\n }\n },\n _threadCount: {\n get: function () {\n return context._threadCount;\n },\n set: function (_threadCount) {\n context._threadCount = _threadCount;\n }\n },\n Consumer: {\n get: function () {\n if (!hasWarnedAboutUsingNestedContextConsumers) {\n hasWarnedAboutUsingNestedContextConsumers = true;\n\n error('Rendering is not supported and will be removed in ' + 'a future major release. Did you mean to render instead?');\n }\n\n return context.Consumer;\n }\n }\n }); // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty\n\n context.Consumer = Consumer;\n }\n\n {\n context._currentRenderer = null;\n context._currentRenderer2 = null;\n }\n\n return context;\n}\n\nfunction lazy(ctor) {\n var lazyType = {\n $$typeof: REACT_LAZY_TYPE,\n _ctor: ctor,\n // React uses these fields to store the result.\n _status: -1,\n _result: null\n };\n\n {\n // In production, this would just set it on the object.\n var defaultProps;\n var propTypes;\n Object.defineProperties(lazyType, {\n defaultProps: {\n configurable: true,\n get: function () {\n return defaultProps;\n },\n set: function (newDefaultProps) {\n error('React.lazy(...): It is not supported to assign `defaultProps` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.');\n\n defaultProps = newDefaultProps; // Match production behavior more closely:\n\n Object.defineProperty(lazyType, 'defaultProps', {\n enumerable: true\n });\n }\n },\n propTypes: {\n configurable: true,\n get: function () {\n return propTypes;\n },\n set: function (newPropTypes) {\n error('React.lazy(...): It is not supported to assign `propTypes` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.');\n\n propTypes = newPropTypes; // Match production behavior more closely:\n\n Object.defineProperty(lazyType, 'propTypes', {\n enumerable: true\n });\n }\n }\n });\n }\n\n return lazyType;\n}\n\nfunction forwardRef(render) {\n {\n if (render != null && render.$$typeof === REACT_MEMO_TYPE) {\n error('forwardRef requires a render function but received a `memo` ' + 'component. Instead of forwardRef(memo(...)), use ' + 'memo(forwardRef(...)).');\n } else if (typeof render !== 'function') {\n error('forwardRef requires a render function but was given %s.', render === null ? 'null' : typeof render);\n } else {\n if (render.length !== 0 && render.length !== 2) {\n error('forwardRef render functions accept exactly two parameters: props and ref. %s', render.length === 1 ? 'Did you forget to use the ref parameter?' : 'Any additional parameter will be undefined.');\n }\n }\n\n if (render != null) {\n if (render.defaultProps != null || render.propTypes != null) {\n error('forwardRef render functions do not support propTypes or defaultProps. ' + 'Did you accidentally pass a React component?');\n }\n }\n }\n\n return {\n $$typeof: REACT_FORWARD_REF_TYPE,\n render: render\n };\n}\n\nfunction isValidElementType(type) {\n return typeof type === 'string' || typeof type === 'function' || // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.\n type === REACT_FRAGMENT_TYPE || type === REACT_CONCURRENT_MODE_TYPE || type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || typeof type === 'object' && type !== null && (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || type.$$typeof === REACT_FUNDAMENTAL_TYPE || type.$$typeof === REACT_RESPONDER_TYPE || type.$$typeof === REACT_SCOPE_TYPE || type.$$typeof === REACT_BLOCK_TYPE);\n}\n\nfunction memo(type, compare) {\n {\n if (!isValidElementType(type)) {\n error('memo: The first argument must be a component. Instead ' + 'received: %s', type === null ? 'null' : typeof type);\n }\n }\n\n return {\n $$typeof: REACT_MEMO_TYPE,\n type: type,\n compare: compare === undefined ? null : compare\n };\n}\n\nfunction resolveDispatcher() {\n var dispatcher = ReactCurrentDispatcher.current;\n\n if (!(dispatcher !== null)) {\n {\n throw Error( \"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\\n1. You might have mismatching versions of React and the renderer (such as React DOM)\\n2. You might be breaking the Rules of Hooks\\n3. You might have more than one copy of React in the same app\\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.\" );\n }\n }\n\n return dispatcher;\n}\n\nfunction useContext(Context, unstable_observedBits) {\n var dispatcher = resolveDispatcher();\n\n {\n if (unstable_observedBits !== undefined) {\n error('useContext() second argument is reserved for future ' + 'use in React. Passing it is not supported. ' + 'You passed: %s.%s', unstable_observedBits, typeof unstable_observedBits === 'number' && Array.isArray(arguments[2]) ? '\\n\\nDid you call array.map(useContext)? ' + 'Calling Hooks inside a loop is not supported. ' + 'Learn more at https://fb.me/rules-of-hooks' : '');\n } // TODO: add a more generic warning for invalid values.\n\n\n if (Context._context !== undefined) {\n var realContext = Context._context; // Don't deduplicate because this legitimately causes bugs\n // and nobody should be using this in existing code.\n\n if (realContext.Consumer === Context) {\n error('Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' + 'removed in a future major release. Did you mean to call useContext(Context) instead?');\n } else if (realContext.Provider === Context) {\n error('Calling useContext(Context.Provider) is not supported. ' + 'Did you mean to call useContext(Context) instead?');\n }\n }\n }\n\n return dispatcher.useContext(Context, unstable_observedBits);\n}\nfunction useState(initialState) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useState(initialState);\n}\nfunction useReducer(reducer, initialArg, init) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useReducer(reducer, initialArg, init);\n}\nfunction useRef(initialValue) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useRef(initialValue);\n}\nfunction useEffect(create, deps) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useEffect(create, deps);\n}\nfunction useLayoutEffect(create, deps) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useLayoutEffect(create, deps);\n}\nfunction useCallback(callback, deps) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useCallback(callback, deps);\n}\nfunction useMemo(create, deps) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useMemo(create, deps);\n}\nfunction useImperativeHandle(ref, create, deps) {\n var dispatcher = resolveDispatcher();\n return dispatcher.useImperativeHandle(ref, create, deps);\n}\nfunction useDebugValue(value, formatterFn) {\n {\n var dispatcher = resolveDispatcher();\n return dispatcher.useDebugValue(value, formatterFn);\n }\n}\n\nvar propTypesMisspellWarningShown;\n\n{\n propTypesMisspellWarningShown = false;\n}\n\nfunction getDeclarationErrorAddendum() {\n if (ReactCurrentOwner.current) {\n var name = getComponentName(ReactCurrentOwner.current.type);\n\n if (name) {\n return '\\n\\nCheck the render method of `' + name + '`.';\n }\n }\n\n return '';\n}\n\nfunction getSourceInfoErrorAddendum(source) {\n if (source !== undefined) {\n var fileName = source.fileName.replace(/^.*[\\\\\\/]/, '');\n var lineNumber = source.lineNumber;\n return '\\n\\nCheck your code at ' + fileName + ':' + lineNumber + '.';\n }\n\n return '';\n}\n\nfunction getSourceInfoErrorAddendumForProps(elementProps) {\n if (elementProps !== null && elementProps !== undefined) {\n return getSourceInfoErrorAddendum(elementProps.__source);\n }\n\n return '';\n}\n/**\n * Warn if there's no key explicitly set on dynamic arrays of children or\n * object keys are not valid. This allows us to keep track of children between\n * updates.\n */\n\n\nvar ownerHasKeyUseWarning = {};\n\nfunction getCurrentComponentErrorInfo(parentType) {\n var info = getDeclarationErrorAddendum();\n\n if (!info) {\n var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name;\n\n if (parentName) {\n info = \"\\n\\nCheck the top-level render call using <\" + parentName + \">.\";\n }\n }\n\n return info;\n}\n/**\n * Warn if the element doesn't have an explicit key assigned to it.\n * This element is in an array. The array could grow and shrink or be\n * reordered. All children that haven't already been validated are required to\n * have a \"key\" property assigned to it. Error statuses are cached so a warning\n * will only be shown once.\n *\n * @internal\n * @param {ReactElement} element Element that requires a key.\n * @param {*} parentType element's parent's type.\n */\n\n\nfunction validateExplicitKey(element, parentType) {\n if (!element._store || element._store.validated || element.key != null) {\n return;\n }\n\n element._store.validated = true;\n var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);\n\n if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {\n return;\n }\n\n ownerHasKeyUseWarning[currentComponentErrorInfo] = true; // Usually the current owner is the offender, but if it accepts children as a\n // property, it may be the creator of the child that's responsible for\n // assigning it a key.\n\n var childOwner = '';\n\n if (element && element._owner && element._owner !== ReactCurrentOwner.current) {\n // Give the component that originally created this child.\n childOwner = \" It was passed a child from \" + getComponentName(element._owner.type) + \".\";\n }\n\n setCurrentlyValidatingElement(element);\n\n {\n error('Each child in a list should have a unique \"key\" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.', currentComponentErrorInfo, childOwner);\n }\n\n setCurrentlyValidatingElement(null);\n}\n/**\n * Ensure that every element either is passed in a static location, in an\n * array with an explicit keys property defined, or in an object literal\n * with valid key property.\n *\n * @internal\n * @param {ReactNode} node Statically passed child of any type.\n * @param {*} parentType node's parent's type.\n */\n\n\nfunction validateChildKeys(node, parentType) {\n if (typeof node !== 'object') {\n return;\n }\n\n if (Array.isArray(node)) {\n for (var i = 0; i < node.length; i++) {\n var child = node[i];\n\n if (isValidElement(child)) {\n validateExplicitKey(child, parentType);\n }\n }\n } else if (isValidElement(node)) {\n // This element was passed in a valid location.\n if (node._store) {\n node._store.validated = true;\n }\n } else if (node) {\n var iteratorFn = getIteratorFn(node);\n\n if (typeof iteratorFn === 'function') {\n // Entry iterators used to provide implicit keys,\n // but now we print a separate warning for them later.\n if (iteratorFn !== node.entries) {\n var iterator = iteratorFn.call(node);\n var step;\n\n while (!(step = iterator.next()).done) {\n if (isValidElement(step.value)) {\n validateExplicitKey(step.value, parentType);\n }\n }\n }\n }\n }\n}\n/**\n * Given an element, validate that its props follow the propTypes definition,\n * provided by the type.\n *\n * @param {ReactElement} element\n */\n\n\nfunction validatePropTypes(element) {\n {\n var type = element.type;\n\n if (type === null || type === undefined || typeof type === 'string') {\n return;\n }\n\n var name = getComponentName(type);\n var propTypes;\n\n if (typeof type === 'function') {\n propTypes = type.propTypes;\n } else if (typeof type === 'object' && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.\n // Inner props are checked in the reconciler.\n type.$$typeof === REACT_MEMO_TYPE)) {\n propTypes = type.propTypes;\n } else {\n return;\n }\n\n if (propTypes) {\n setCurrentlyValidatingElement(element);\n checkPropTypes(propTypes, element.props, 'prop', name, ReactDebugCurrentFrame.getStackAddendum);\n setCurrentlyValidatingElement(null);\n } else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {\n propTypesMisspellWarningShown = true;\n\n error('Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', name || 'Unknown');\n }\n\n if (typeof type.getDefaultProps === 'function' && !type.getDefaultProps.isReactClassApproved) {\n error('getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.');\n }\n }\n}\n/**\n * Given a fragment, validate that it can only be provided with fragment props\n * @param {ReactElement} fragment\n */\n\n\nfunction validateFragmentProps(fragment) {\n {\n setCurrentlyValidatingElement(fragment);\n var keys = Object.keys(fragment.props);\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i];\n\n if (key !== 'children' && key !== 'key') {\n error('Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key);\n\n break;\n }\n }\n\n if (fragment.ref !== null) {\n error('Invalid attribute `ref` supplied to `React.Fragment`.');\n }\n\n setCurrentlyValidatingElement(null);\n }\n}\nfunction createElementWithValidation(type, props, children) {\n var validType = isValidElementType(type); // We warn in this case but don't throw. We expect the element creation to\n // succeed and there will likely be errors in render.\n\n if (!validType) {\n var info = '';\n\n if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {\n info += ' You likely forgot to export your component from the file ' + \"it's defined in, or you might have mixed up default and named imports.\";\n }\n\n var sourceInfo = getSourceInfoErrorAddendumForProps(props);\n\n if (sourceInfo) {\n info += sourceInfo;\n } else {\n info += getDeclarationErrorAddendum();\n }\n\n var typeString;\n\n if (type === null) {\n typeString = 'null';\n } else if (Array.isArray(type)) {\n typeString = 'array';\n } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {\n typeString = \"<\" + (getComponentName(type.type) || 'Unknown') + \" />\";\n info = ' Did you accidentally export a JSX literal instead of a component?';\n } else {\n typeString = typeof type;\n }\n\n {\n error('React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, info);\n }\n }\n\n var element = createElement.apply(this, arguments); // The result can be nullish if a mock or a custom function is used.\n // TODO: Drop this when these are no longer allowed as the type argument.\n\n if (element == null) {\n return element;\n } // Skip key warning if the type isn't valid since our key validation logic\n // doesn't expect a non-string/function type and can throw confusing errors.\n // We don't want exception behavior to differ between dev and prod.\n // (Rendering will throw with a helpful message and as soon as the type is\n // fixed, the key warnings will appear.)\n\n\n if (validType) {\n for (var i = 2; i < arguments.length; i++) {\n validateChildKeys(arguments[i], type);\n }\n }\n\n if (type === REACT_FRAGMENT_TYPE) {\n validateFragmentProps(element);\n } else {\n validatePropTypes(element);\n }\n\n return element;\n}\nvar didWarnAboutDeprecatedCreateFactory = false;\nfunction createFactoryWithValidation(type) {\n var validatedFactory = createElementWithValidation.bind(null, type);\n validatedFactory.type = type;\n\n {\n if (!didWarnAboutDeprecatedCreateFactory) {\n didWarnAboutDeprecatedCreateFactory = true;\n\n warn('React.createFactory() is deprecated and will be removed in ' + 'a future major release. Consider using JSX ' + 'or use React.createElement() directly instead.');\n } // Legacy hook: remove it\n\n\n Object.defineProperty(validatedFactory, 'type', {\n enumerable: false,\n get: function () {\n warn('Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.');\n\n Object.defineProperty(this, 'type', {\n value: type\n });\n return type;\n }\n });\n }\n\n return validatedFactory;\n}\nfunction cloneElementWithValidation(element, props, children) {\n var newElement = cloneElement.apply(this, arguments);\n\n for (var i = 2; i < arguments.length; i++) {\n validateChildKeys(arguments[i], newElement.type);\n }\n\n validatePropTypes(newElement);\n return newElement;\n}\n\n{\n\n try {\n var frozenObject = Object.freeze({});\n var testMap = new Map([[frozenObject, null]]);\n var testSet = new Set([frozenObject]); // This is necessary for Rollup to not consider these unused.\n // https://github.com/rollup/rollup/issues/1771\n // TODO: we can remove these if Rollup fixes the bug.\n\n testMap.set(0, 0);\n testSet.add(0);\n } catch (e) {\n }\n}\n\nvar createElement$1 = createElementWithValidation ;\nvar cloneElement$1 = cloneElementWithValidation ;\nvar createFactory = createFactoryWithValidation ;\nvar Children = {\n map: mapChildren,\n forEach: forEachChildren,\n count: countChildren,\n toArray: toArray,\n only: onlyChild\n};\n\nexports.Children = Children;\nexports.Component = Component;\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.Profiler = REACT_PROFILER_TYPE;\nexports.PureComponent = PureComponent;\nexports.StrictMode = REACT_STRICT_MODE_TYPE;\nexports.Suspense = REACT_SUSPENSE_TYPE;\nexports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals;\nexports.cloneElement = cloneElement$1;\nexports.createContext = createContext;\nexports.createElement = createElement$1;\nexports.createFactory = createFactory;\nexports.createRef = createRef;\nexports.forwardRef = forwardRef;\nexports.isValidElement = isValidElement;\nexports.lazy = lazy;\nexports.memo = memo;\nexports.useCallback = useCallback;\nexports.useContext = useContext;\nexports.useDebugValue = useDebugValue;\nexports.useEffect = useEffect;\nexports.useImperativeHandle = useImperativeHandle;\nexports.useLayoutEffect = useLayoutEffect;\nexports.useMemo = useMemo;\nexports.useReducer = useReducer;\nexports.useRef = useRef;\nexports.useState = useState;\nexports.version = ReactVersion;\n })();\n}\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/react/cjs/react.development.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/node_modules/react/index.js": +/*!**********************************************************!*\ + !*** ../ckeditor5-inspector/node_modules/react/index.js ***! + \**********************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\nif (false) {} else {\n module.exports = __webpack_require__(/*! ./cjs/react.development.js */ \"../ckeditor5-inspector/node_modules/react/cjs/react.development.js\");\n}\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/node_modules/react/index.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/tree.css": +/*!***********************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/tree.css ***! + \***********************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("var api = __webpack_require__(/*! ../../../../ckeditor5-mini-inspector/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n var content = __webpack_require__(/*! !../../../../ckeditor5-mini-inspector/node_modules/css-loader/dist/cjs.js??ref--5-1!../../../../ckeditor5-mini-inspector/node_modules/postcss-loader/src??ref--5-2!./tree.css */ \"./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!../ckeditor5-inspector/src/components/tree/tree.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.i, content, '']];\n }\n\nvar options = {\"injectType\":\"styleTag\",\"attributes\":{\"data-cke-inspector\":true}};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nmodule.exports = content.locals || {};\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/tree.css?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/tree.js": +/*!**********************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/tree.js ***! + \**********************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Tree; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \"../ckeditor5-inspector/src/components/tree/utils.js\");\n/* harmony import */ var _tree_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tree.css */ \"../ckeditor5-inspector/src/components/tree/tree.css\");\n/* harmony import */ var _tree_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_tree_css__WEBPACK_IMPORTED_MODULE_2__);\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/tree.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\n/**\n * A class representing a tree of nodes.\n */\n\nclass Tree extends react__WEBPACK_IMPORTED_MODULE_0__[\"Component\"] {\n render() {\n let treeContent;\n\n if (this.props.definition) {\n treeContent = this.props.definition.map((definition, index) => {\n return Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\"renderTreeNodeFromDefinition\"])(definition, index, {\n onClick: this.props.onClick,\n showCompactText: this.props.showCompactText,\n showElementTypes: this.props.showElementTypes,\n activeNode: this.props.activeNode\n });\n });\n } else {\n treeContent = 'Nothing to show.';\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: ['ck-inspector-tree', ...(this.props.className || []), this.props.textDirection ? 'ck-inspector-tree_text-direction_' + this.props.textDirection : '', this.props.showCompactText ? 'ck-inspector-tree_compact-text' : ''].join(' '),\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 30,\n columnNumber: 10\n }\n }, treeContent);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/tree.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treecomment.js": +/*!*****************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treecomment.js ***! + \*****************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreeComment; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/treecomment.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n/**\n * A class which instances represent comments in the tree.\n */\n\nclass TreeComment extends react__WEBPACK_IMPORTED_MODULE_0__[\"Component\"] {\n render() {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-comment\",\n dangerouslySetInnerHTML: {\n __html: this.props.definition.text\n },\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 13,\n columnNumber: 10\n }\n });\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treecomment.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treeelement.js": +/*!*****************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treeelement.js ***! + \*****************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreeElement; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash.isequal */ \"../ckeditor5-inspector/node_modules/lodash.isequal/index.js\");\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash_isequal__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _treenode__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./treenode */ \"../ckeditor5-inspector/src/components/tree/treenode.js\");\n/* harmony import */ var _treenodeattribute__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./treenodeattribute */ \"../ckeditor5-inspector/src/components/tree/treenodeattribute.js\");\n/* harmony import */ var _treeposition__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./treeposition */ \"../ckeditor5-inspector/src/components/tree/treeposition.js\");\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/treeelement.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\n\n\n/**\n * A class which instances represent elements in the tree.\n */\n\nclass TreeElement extends _treenode__WEBPACK_IMPORTED_MODULE_2__[\"default\"] {\n render() {\n const definition = this.definition;\n const presentation = definition.presentation;\n const isEmpty = presentation && presentation.isEmpty;\n const cssClass = presentation && presentation.cssClass;\n const children = this.getChildren();\n const nodeClasses = ['ck-inspector-code', 'ck-inspector-tree-node', this.isActive ? 'ck-inspector-tree-node_active' : '', isEmpty ? 'ck-inspector-tree-node_empty' : '', cssClass];\n const beforeNodeName = [];\n const afterNodeName = [];\n\n if (definition.positionsBefore) {\n definition.positionsBefore.forEach((position, index) => {\n beforeNodeName.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position-before:' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 35,\n columnNumber: 26\n }\n }));\n });\n }\n\n if (definition.positionsAfter) {\n definition.positionsAfter.forEach((position, index) => {\n afterNodeName.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position-after:' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 41,\n columnNumber: 25\n }\n }));\n });\n }\n\n if (definition.positions) {\n definition.positions.forEach((position, index) => {\n children.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 47,\n columnNumber: 20\n }\n }));\n });\n }\n\n let nameToDisplay = definition.name;\n\n if (this.globalTreeProps.showElementTypes) {\n nameToDisplay = definition.elementType + ':' + nameToDisplay;\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: nodeClasses.join(' '),\n onClick: this.handleClick,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 57,\n columnNumber: 10\n }\n }, beforeNodeName, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 59,\n columnNumber: 4\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name__bracket ck-inspector-tree-node__name__bracket_open\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 60,\n columnNumber: 5\n }\n }), nameToDisplay, this.getAttributes(), isEmpty ? '' : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name__bracket ck-inspector-tree-node__name__bracket_close\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 63,\n columnNumber: 21\n }\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"ck-inspector-tree-node__content\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 65,\n columnNumber: 4\n }\n }, children), isEmpty ? '' : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name ck-inspector-tree-node__name_close\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 69,\n columnNumber: 5\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name__bracket ck-inspector-tree-node__name__bracket_open\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 70,\n columnNumber: 6\n }\n }), \"/\", nameToDisplay, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__name__bracket ck-inspector-tree-node__name__bracket_close\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 72,\n columnNumber: 6\n }\n }), afterNodeName));\n }\n\n getAttributes() {\n const attributes = [];\n const definition = this.definition;\n\n for (const [key, value] of definition.attributes) {\n attributes.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treenodeattribute__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n key: key,\n name: key,\n value: value,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 84,\n columnNumber: 21\n }\n }));\n }\n\n return attributes;\n }\n\n shouldComponentUpdate(nextProps) {\n return !lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default()(this.props, nextProps);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treeelement.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treenode.js": +/*!**************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treenode.js ***! + \**************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreeNode; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \"../ckeditor5-inspector/src/components/tree/utils.js\");\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash.isequal */ \"../ckeditor5-inspector/node_modules/lodash.isequal/index.js\");\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(lodash_isequal__WEBPACK_IMPORTED_MODULE_2__);\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\n/**\n * A base class for nodes in the tree.\n */\n\nclass TreeNode extends react__WEBPACK_IMPORTED_MODULE_0__[\"Component\"] {\n constructor(props) {\n super(props);\n this.handleClick = this.handleClick.bind(this);\n }\n\n handleClick(evt) {\n this.globalTreeProps.onClick(evt, this.definition.node);\n }\n\n getChildren() {\n return this.definition.children.map((childDefinition, index) => {\n return Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\"renderTreeNodeFromDefinition\"])(childDefinition, index, this.props.globalTreeProps);\n });\n }\n\n get definition() {\n return this.props.definition;\n }\n\n get globalTreeProps() {\n return this.props.globalTreeProps || {};\n }\n\n get isActive() {\n return this.definition.node === this.globalTreeProps.activeNode;\n }\n\n shouldComponentUpdate(nextProps) {\n return !lodash_isequal__WEBPACK_IMPORTED_MODULE_2___default()(this.props, nextProps);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treenode.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treenodeattribute.js": +/*!***********************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treenodeattribute.js ***! + \***********************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreeNodeAttribute; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ \"../ckeditor5-inspector/src/components/utils.js\");\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/treenodeattribute.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 500;\n/**\n * A class which instances represent attributes in the tree.\n */\n\nclass TreeNodeAttribute extends react__WEBPACK_IMPORTED_MODULE_0__[\"PureComponent\"] {\n render() {\n let valueElement;\n const value = Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\"truncateString\"])(this.props.value, MAX_ATTRIBUTE_VALUE_LENGTH);\n\n if (!this.props.dontRenderValue) {\n valueElement = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__attribute__value\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 20,\n columnNumber: 19\n }\n }, value);\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__attribute\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 25,\n columnNumber: 10\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__attribute__name\",\n title: value,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 26,\n columnNumber: 4\n }\n }, this.props.name), valueElement);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treenodeattribute.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treeposition.js": +/*!******************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treeposition.js ***! + \******************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreePosition; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash.isequal */ \"../ckeditor5-inspector/node_modules/lodash.isequal/index.js\");\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash_isequal__WEBPACK_IMPORTED_MODULE_1__);\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/treeposition.js\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n/**\n * A class which instances represent positions (selection, markers) in the tree.\n */\n\nclass TreePosition extends react__WEBPACK_IMPORTED_MODULE_0__[\"Component\"] {\n render() {\n const definition = this.props.definition;\n const attrs = {\n className: ['ck-inspector-tree__position', definition.type === 'selection' ? 'ck-inspector-tree__position_selection' : '', definition.type === 'marker' ? 'ck-inspector-tree__position_marker' : '', definition.isEnd ? 'ck-inspector-tree__position_end' : ''].join(' '),\n style: {}\n };\n\n if (definition.presentation && definition.presentation.color) {\n attrs.style['--ck-inspector-color-tree-position'] = definition.presentation.color;\n }\n\n if (definition.type === 'marker') {\n attrs['data-marker-name'] = definition.name;\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", _extends({}, attrs, {\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 33,\n columnNumber: 10\n }\n }), \"\\u200B\");\n }\n\n shouldComponentUpdate(nextProps) {\n return !lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default()(this.props, nextProps);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treeposition.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/treetextnode.js": +/*!******************************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/treetextnode.js ***! + \******************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return TreeTextNode; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash.isequal */ \"../ckeditor5-inspector/node_modules/lodash.isequal/index.js\");\n/* harmony import */ var lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash_isequal__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _treenode__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./treenode */ \"../ckeditor5-inspector/src/components/tree/treenode.js\");\n/* harmony import */ var _treenodeattribute__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./treenodeattribute */ \"../ckeditor5-inspector/src/components/tree/treenodeattribute.js\");\n/* harmony import */ var _treeposition__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./treeposition */ \"../ckeditor5-inspector/src/components/tree/treeposition.js\");\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/treetextnode.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\n\n\n/**\n * A class which instances represent text in the tree.\n */\n\nclass TreeTextNode extends _treenode__WEBPACK_IMPORTED_MODULE_2__[\"default\"] {\n render() {\n const definition = this.definition;\n const classes = ['ck-inspector-tree-text', this.isActive ? 'ck-inspector-tree-node_active' : ''].join(' ');\n let nodeText = this.definition.text;\n\n if (definition.positions && definition.positions.length) {\n nodeText = nodeText.split(''); // Don't alter the props (sort&reverse would do that in place) because next time it will cause unnecessary\n // rendering due to equality check indicating old and new props are different arrays.\n\n Array.from(definition.positions).sort((posA, posB) => {\n if (posA.offset < posB.offset) {\n return -1;\n } else if (posA.offset === posB.offset) {\n return 0;\n } else {\n return 1;\n }\n }).reverse().forEach((position, index) => {\n nodeText.splice(position.offset - definition.startOffset, 0, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 46,\n columnNumber: 7\n }\n }));\n });\n }\n\n const children = [nodeText];\n\n if (definition.positionsBefore && definition.positionsBefore.length) {\n definition.positionsBefore.forEach((position, index) => {\n children.unshift( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position-before:' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 55,\n columnNumber: 23\n }\n }));\n });\n }\n\n if (definition.positionsAfter && definition.positionsAfter.length) {\n definition.positionsAfter.forEach((position, index) => {\n children.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeposition__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n key: 'position-after:' + index,\n definition: position,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 61,\n columnNumber: 20\n }\n }));\n });\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: classes,\n onClick: this.handleClick,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 65,\n columnNumber: 10\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-node__content\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 66,\n columnNumber: 4\n }\n }, this.globalTreeProps.showCompactText ? '' : this.getAttributes(), this.globalTreeProps.showCompactText ? '' : '\"', children, this.globalTreeProps.showCompactText ? '' : '\"'));\n }\n\n getAttributes() {\n const attributes = [];\n const definition = this.definition;\n const presentation = definition.presentation;\n const dontRenderAttributeValue = presentation && presentation.dontRenderAttributeValue;\n\n for (const [key, value] of definition.attributes) {\n attributes.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treenodeattribute__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n key: key,\n name: key,\n value: value,\n dontRenderValue: dontRenderAttributeValue,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 82,\n columnNumber: 21\n }\n }));\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"ck-inspector-tree-text__attributes\",\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 85,\n columnNumber: 10\n }\n }, attributes);\n }\n\n shouldComponentUpdate(nextProps) {\n return !lodash_isequal__WEBPACK_IMPORTED_MODULE_1___default()(this.props, nextProps);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/treetextnode.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/tree/utils.js": +/*!***********************************************************!*\ + !*** ../ckeditor5-inspector/src/components/tree/utils.js ***! + \***********************************************************/ +/*! exports provided: renderTreeNodeFromDefinition */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"renderTreeNodeFromDefinition\", function() { return renderTreeNodeFromDefinition; });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"../ckeditor5-inspector/node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _treeelement__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./treeelement */ \"../ckeditor5-inspector/src/components/tree/treeelement.js\");\n/* harmony import */ var _treetextnode__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./treetextnode */ \"../ckeditor5-inspector/src/components/tree/treetextnode.js\");\n/* harmony import */ var _treecomment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./treecomment */ \"../ckeditor5-inspector/src/components/tree/treecomment.js\");\nvar _jsxFileName = \"/Users/dawid/Documents/CKSource/CKEditor5/inspector/ckeditor5-inspector/src/components/tree/utils.js\";\n\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\n\nfunction renderTreeNodeFromDefinition(definition, index, globalTreeProps) {\n if (definition.type === 'element') {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treeelement__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n key: index,\n definition: definition,\n globalTreeProps: globalTreeProps,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 14,\n columnNumber: 10\n }\n });\n } else if (definition.type === 'text') {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treetextnode__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n key: index,\n definition: definition,\n globalTreeProps: globalTreeProps,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 16,\n columnNumber: 10\n }\n });\n } else if (definition.type === 'comment') {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_treecomment__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n key: index,\n definition: definition,\n __self: this,\n __source: {\n fileName: _jsxFileName,\n lineNumber: 18,\n columnNumber: 10\n }\n });\n }\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/components/utils.js": +/*!******************************************************!*\ + !*** ../ckeditor5-inspector/src/components/utils.js ***! + \******************************************************/ +/*! exports provided: stringify, uid, stringifyPropertyList, truncateString */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"stringify\", function() { return stringify; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uid\", function() { return uid; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"stringifyPropertyList\", function() { return stringifyPropertyList; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"truncateString\", function() { return truncateString; });\n/* harmony import */ var javascript_stringify__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! javascript-stringify */ \"../ckeditor5-inspector/node_modules/javascript-stringify/dist/index.js\");\n/* harmony import */ var javascript_stringify__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(javascript_stringify__WEBPACK_IMPORTED_MODULE_0__);\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\nfunction stringify(value, quotesAroundText = true) {\n if (value === undefined) {\n return 'undefined';\n }\n\n if (typeof value === 'function') {\n return 'function() {…}';\n }\n\n const stringified = Object(javascript_stringify__WEBPACK_IMPORTED_MODULE_0__[\"stringify\"])(value, stringifySingleToDoubleQuotesReplacer, null, {\n maxDepth: 1\n }); // Note: Remove leading and trailing quotes (\") from the output. By default it is:\n //\n //\t\tJSON.stringify( 'foo' ) => '\"foo\"'\n //\t\tJSON.stringify( true ) => '\"true\"'\n //\t\tJSON.stringify( {} ) => '{}'\n //\t\tJSON.stringify( [] ) => '[]'\n //\n // What should be returned:\n //\n //\t\tstringify( 'foo' ) => 'foo'\n //\t\tstringify( true ) => 'true'\n //\t\tstringify( {} ) => '{}'\n //\t\tstringify( [] ) => '[]'\n //\n\n if (!quotesAroundText) {\n return stringified.replace(/(^\"|\"$)/g, '');\n }\n\n return stringified;\n}\nfunction uid() {\n return Math.random().toString(36).substring(7);\n}\nfunction stringifyPropertyList(list) {\n const stringified = {};\n\n for (const name in list) {\n stringified[name] = list[name];\n stringified[name].value = stringify(stringified[name].value);\n }\n\n return stringified;\n}\nfunction truncateString(string, length) {\n if (string.length > length) {\n return string.substr(0, length) + `… [${string.length - length} characters left]`;\n }\n\n return string;\n} // Unlike JSON.stringify(), javascript-stringify returns single quotes around text.\n// Retain the JSON.stringify() formatting instead of fixing 100 tests across the project :)\n\nfunction stringifySingleToDoubleQuotesReplacer(value, indent, stringify) {\n if (typeof value === 'string') {\n return `\"${value.replace('\\'', '\"')}\"`;\n }\n\n return stringify(value);\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/logger.js": +/*!********************************************!*\ + !*** ../ckeditor5-inspector/src/logger.js ***! + \********************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Logger; });\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n/* global console */\nclass Logger {\n static group(...args) {\n console.group(...args);\n }\n\n static groupEnd(...args) {\n console.groupEnd(...args);\n }\n\n static log(...args) {\n console.log(...args);\n }\n\n static warn(...args) {\n console.warn(...args);\n }\n\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/logger.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/model/data/utils.js": +/*!******************************************************!*\ + !*** ../ckeditor5-inspector/src/model/data/utils.js ***! + \******************************************************/ +/*! exports provided: getEditorModelRoots, getEditorModelRanges, getEditorModelMarkers, getEditorModelTreeDefinition, getEditorModelNodeDefinition */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorModelRoots\", function() { return getEditorModelRoots; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorModelRanges\", function() { return getEditorModelRanges; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorModelMarkers\", function() { return getEditorModelMarkers; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorModelTreeDefinition\", function() { return getEditorModelTreeDefinition; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorModelNodeDefinition\", function() { return getEditorModelNodeDefinition; });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../utils */ \"../ckeditor5-inspector/src/model/utils.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils */ \"../ckeditor5-inspector/src/utils.js\");\n/* harmony import */ var _components_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../components/utils */ \"../ckeditor5-inspector/src/components/utils.js\");\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\nconst DOCS_URL_PREFIX = 'https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_';\nconst MARKER_COLORS = ['#03a9f4', '#fb8c00', '#009688', '#e91e63', '#4caf50', '#00bcd4', '#607d8b', '#cddc39', '#9c27b0', '#f44336', '#6d4c41', '#8bc34a', '#3f51b5', '#2196f3', '#f4511e', '#673ab7', '#ffb300'];\nfunction getEditorModelRoots(editor) {\n if (!editor) {\n return [];\n }\n\n const roots = [...editor.model.document.roots]; // Put $graveyard at the end.\n\n return roots.filter(({\n rootName\n }) => rootName !== '$graveyard').concat(roots.filter(({\n rootName\n }) => rootName === '$graveyard'));\n}\nfunction getEditorModelRanges(editor, currentRootName) {\n if (!editor) {\n return [];\n }\n\n const ranges = [];\n const model = editor.model;\n\n for (const range of model.document.selection.getRanges()) {\n if (range.root.rootName !== currentRootName) {\n continue;\n }\n\n ranges.push({\n type: 'selection',\n start: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getModelPositionDefinition\"])(range.start),\n end: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getModelPositionDefinition\"])(range.end)\n });\n }\n\n return ranges;\n}\nfunction getEditorModelMarkers(editor, currentRootName) {\n if (!editor) {\n return [];\n }\n\n const markers = [];\n const model = editor.model;\n let markerCount = 0;\n\n for (const marker of model.markers) {\n const {\n name,\n affectsData,\n managedUsingOperations\n } = marker;\n const start = marker.getStart();\n const end = marker.getEnd();\n\n if (start.root.rootName !== currentRootName) {\n continue;\n }\n\n markers.push({\n type: 'marker',\n marker,\n name,\n affectsData,\n managedUsingOperations,\n presentation: {\n // When there are more markers than colors, let's start over and reuse\n // the colors.\n color: MARKER_COLORS[markerCount++ % (MARKER_COLORS.length - 1)]\n },\n start: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getModelPositionDefinition\"])(start),\n end: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getModelPositionDefinition\"])(end)\n });\n }\n\n return markers;\n}\nfunction getEditorModelTreeDefinition({\n currentEditor,\n currentRootName,\n ranges,\n markers\n}) {\n if (!currentEditor) {\n return [];\n }\n\n const model = currentEditor.model;\n const modelRoot = model.document.getRoot(currentRootName);\n return [getModelNodeDefinition(modelRoot, [...ranges, ...markers])];\n}\nfunction getEditorModelNodeDefinition(currentEditor, node) {\n const definition = {\n editorNode: node,\n properties: {},\n attributes: {}\n };\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isModelElement\"])(node)) {\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isModelRoot\"])(node)) {\n definition.type = 'RootElement';\n definition.name = node.rootName;\n definition.url = `${DOCS_URL_PREFIX}rootelement-RootElement.html`;\n } else {\n definition.type = 'Element';\n definition.name = node.name;\n definition.url = `${DOCS_URL_PREFIX}element-Element.html`;\n }\n\n definition.properties = {\n childCount: {\n value: node.childCount\n },\n startOffset: {\n value: node.startOffset\n },\n endOffset: {\n value: node.endOffset\n },\n maxOffset: {\n value: node.maxOffset\n }\n };\n } else {\n definition.name = node.data;\n definition.type = 'Text';\n definition.url = `${DOCS_URL_PREFIX}text-Text.html`;\n definition.properties = {\n startOffset: {\n value: node.startOffset\n },\n endOffset: {\n value: node.endOffset\n },\n offsetSize: {\n value: node.offsetSize\n }\n };\n }\n\n definition.properties.path = {\n value: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getNodePathString\"])(node)\n };\n getSortedNodeAttributes(node).forEach(([name, value]) => {\n definition.attributes[name] = {\n value\n };\n });\n definition.properties = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(definition.properties);\n definition.attributes = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(definition.attributes);\n\n for (const attribute in definition.attributes) {\n const attributePropertyDefinitions = {};\n const attirbuteProperties = currentEditor.model.schema.getAttributeProperties(attribute);\n\n for (const name in attirbuteProperties) {\n attributePropertyDefinitions[name] = {\n value: attirbuteProperties[name]\n };\n }\n\n definition.attributes[attribute].subProperties = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(attributePropertyDefinitions);\n }\n\n return definition;\n}\n\nfunction getModelNodeDefinition(node, ranges) {\n const nodeDefinition = {};\n const {\n startOffset,\n endOffset\n } = node;\n Object.assign(nodeDefinition, {\n startOffset,\n endOffset,\n node,\n path: node.getPath(),\n positionsBefore: [],\n positionsAfter: []\n });\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isModelElement\"])(node)) {\n fillElementDefinition(nodeDefinition, ranges);\n } else {\n fillTextNodeDefinition(nodeDefinition);\n }\n\n return nodeDefinition;\n}\n\nfunction fillElementDefinition(elementDefinition, ranges) {\n const element = elementDefinition.node;\n Object.assign(elementDefinition, {\n type: 'element',\n name: element.name,\n children: [],\n maxOffset: element.maxOffset,\n positions: []\n });\n\n for (const child of element.getChildren()) {\n elementDefinition.children.push(getModelNodeDefinition(child, ranges));\n }\n\n fillElementPositions(elementDefinition, ranges);\n elementDefinition.attributes = getNodeAttributesForDefinition(element);\n}\n\nfunction fillElementPositions(elementDefinition, ranges) {\n for (const range of ranges) {\n const positions = getRangePositionsInElement(elementDefinition, range);\n\n for (const position of positions) {\n const offset = position.offset;\n\n if (offset === 0) {\n const firstChild = elementDefinition.children[0];\n\n if (firstChild) {\n firstChild.positionsBefore.push(position);\n } else {\n elementDefinition.positions.push(position);\n }\n } else if (offset === elementDefinition.maxOffset) {\n const lastChild = elementDefinition.children[elementDefinition.children.length - 1];\n\n if (lastChild) {\n lastChild.positionsAfter.push(position);\n } else {\n elementDefinition.positions.push(position);\n }\n } else {\n // Go backward when looking for a child that will host the end position.\n // Go forward when looking for a child that will host the start position.\n //\n //\t\t\n //\t\t[]\n //\t\t\n //\n // instead of\n //\n //\t\t[\n //\t\t\n //\t\t]\n //\n let childIndex = position.isEnd ? 0 : elementDefinition.children.length - 1;\n let child = elementDefinition.children[childIndex];\n\n while (child) {\n if (child.startOffset === offset) {\n child.positionsBefore.push(position);\n break;\n }\n\n if (child.endOffset === offset) {\n const nextChild = elementDefinition.children[childIndex + 1];\n const isBetweenTextAndElement = child.type === 'text' && nextChild && nextChild.type === 'element';\n const isBetweenElementAndText = child.type === 'element' && nextChild && nextChild.type === 'text';\n const isBetweenTwoTexts = child.type === 'text' && nextChild && nextChild.type === 'text'; // Avoid the situation where the order of positions could weird around text nodes.\n //\n //\t\tdo <$text>foo<$/text><$text>[]bar<$/text>\n //\t\tinstead of <$text>foo]<$/text><$text>[bar<$/text>\n //\n //\t\tdo
        []bar<$/text>
        \n //\t\tinstead of
        [<$text>]bar<$/text>
        \n //\n //\t\tdo bar<$/text>[]
        \n //\t\tinstead of <$text>bar[<$/text>]
        \n //\n\n if (position.isEnd && (isBetweenTextAndElement || isBetweenElementAndText || isBetweenTwoTexts)) {\n nextChild.positionsBefore.push(position);\n } else {\n child.positionsAfter.push(position);\n }\n\n break;\n }\n\n if (child.startOffset < offset && child.endOffset > offset) {\n child.positions.push(position);\n break;\n }\n\n childIndex += position.isEnd ? 1 : -1;\n child = elementDefinition.children[childIndex];\n }\n }\n }\n }\n}\n\nfunction fillTextNodeDefinition(textNodeDefinition) {\n const textNode = textNodeDefinition.node;\n Object.assign(textNodeDefinition, {\n type: 'text',\n text: textNode.data,\n positions: [],\n presentation: {\n dontRenderAttributeValue: true\n }\n });\n textNodeDefinition.attributes = getNodeAttributesForDefinition(textNode);\n}\n\nfunction getNodeAttributesForDefinition(node) {\n const attrs = getSortedNodeAttributes(node).map(([name, value]) => {\n return [name, Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringify\"])(value, false)];\n });\n return new Map(attrs);\n}\n\nfunction getSortedNodeAttributes(node) {\n return [...node.getAttributes()].sort(([nameA], [nameB]) => nameA < nameB ? -1 : 1);\n}\n\nfunction getRangePositionsInElement(node, range) {\n const nodePath = node.path;\n const startPath = range.start.path;\n const endPath = range.end.path;\n const positions = [];\n\n if (isPathPrefixingAnother(nodePath, startPath)) {\n positions.push({\n offset: startPath[startPath.length - 1],\n isEnd: false,\n presentation: range.presentation || null,\n type: range.type,\n name: range.name || null\n });\n }\n\n if (isPathPrefixingAnother(nodePath, endPath)) {\n positions.push({\n offset: endPath[endPath.length - 1],\n isEnd: true,\n presentation: range.presentation || null,\n type: range.type,\n name: range.name || null\n });\n }\n\n return positions;\n}\n\nfunction isPathPrefixingAnother(pathA, pathB) {\n if (pathA.length === pathB.length - 1) {\n const comparison = Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\"compareArrays\"])(pathA, pathB);\n\n if (comparison === 'prefix') {\n return true;\n }\n }\n\n return false;\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/model/data/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/model/utils.js": +/*!*************************************************!*\ + !*** ../ckeditor5-inspector/src/model/utils.js ***! + \*************************************************/ +/*! exports provided: isModelElement, isModelRoot, getNodePathString, getModelPositionDefinition */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isModelElement\", function() { return isModelElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isModelRoot\", function() { return isModelRoot; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getNodePathString\", function() { return getNodePathString; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getModelPositionDefinition\", function() { return getModelPositionDefinition; });\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\nfunction isModelElement(node) {\n return node && node.is('element');\n}\nfunction isModelRoot(node) {\n return node && node.is('rootElement');\n}\nfunction getNodePathString(node) {\n return node.getPath ? node.getPath() : node.path;\n}\nfunction getModelPositionDefinition(position) {\n return {\n path: getNodePathString(position),\n stickiness: position.stickiness,\n index: position.index,\n isAtEnd: position.isAtEnd,\n isAtStart: position.isAtStart,\n offset: position.offset,\n textNode: position.textNode && position.textNode.data\n };\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/model/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/ui.css": +/*!*****************************************!*\ + !*** ../ckeditor5-inspector/src/ui.css ***! + \*****************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("var api = __webpack_require__(/*! ../../ckeditor5-mini-inspector/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n var content = __webpack_require__(/*! !../../ckeditor5-mini-inspector/node_modules/css-loader/dist/cjs.js??ref--5-1!../../ckeditor5-mini-inspector/node_modules/postcss-loader/src??ref--5-2!./ui.css */ \"./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!../ckeditor5-inspector/src/ui.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.i, content, '']];\n }\n\nvar options = {\"injectType\":\"styleTag\",\"attributes\":{\"data-cke-inspector\":true}};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nmodule.exports = content.locals || {};\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/ui.css?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/utils.js": +/*!*******************************************!*\ + !*** ../ckeditor5-inspector/src/utils.js ***! + \*******************************************/ +/*! exports provided: normalizeArguments, getFirstEditor, getFirstEditorName, compareArrays */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"normalizeArguments\", function() { return normalizeArguments; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getFirstEditor\", function() { return getFirstEditor; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getFirstEditorName\", function() { return getFirstEditorName; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"compareArrays\", function() { return compareArrays; });\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./logger */ \"../ckeditor5-inspector/src/logger.js\");\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\nlet unnamedEditorCount = 0;\nfunction normalizeArguments(args) {\n const normalized = {\n editors: {},\n options: {}\n }; // Deprecated // attach( 'name', editor );\n\n if (typeof args[0] === 'string') {\n _logger__WEBPACK_IMPORTED_MODULE_0__[\"default\"].warn(`[CKEditorInspector] The CKEditorInspector.attach( '${args[0]}', editor ) syntax has been deprecated ` + 'and will be removed in the near future. To pass a name of an editor instance, use ' + `CKEditorInspector.attach( { '${args[0]}': editor } ) instead. ` + 'Learn more in https://github.com/ckeditor/ckeditor5-inspector/blob/master/README.md.');\n normalized.editors[args[0]] = args[1];\n } else {\n // attach( editor );\n if (isEditorInstance(args[0])) {\n normalized.editors[getNextEditorName()] = args[0];\n } // attach( { foo: editor1, bar: editor2, ... } );\n else {\n for (const name in args[0]) {\n normalized.editors[name] = args[0][name];\n }\n } // attach( ..., { options } );\n\n\n normalized.options = args[1] || normalized.options;\n }\n\n return normalized;\n}\n\nfunction getNextEditorName() {\n return `editor-${++unnamedEditorCount}`;\n}\n\nfunction getFirstEditor(editors) {\n return [...editors][0][1];\n}\nfunction getFirstEditorName(editors) {\n return [...editors][0][0] || '';\n}\n\nfunction isEditorInstance(arg) {\n // Quack! 🦆\n return !!arg.model && !!arg.editing;\n}\n\nfunction compareArrays(a, b) {\n const minLen = Math.min(a.length, b.length);\n\n for (let i = 0; i < minLen; i++) {\n if (a[i] != b[i]) {\n // The arrays are different.\n return i;\n }\n } // Both arrays were same at all points.\n\n\n if (a.length == b.length) {\n // If their length is also same, they are the same.\n return 'same';\n } else if (a.length < b.length) {\n // Compared array is shorter so it is a prefix of the other array.\n return 'prefix';\n } else {\n // Compared array is longer so it is an extension of the other array.\n return 'extension';\n }\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/view/data/utils.js": +/*!*****************************************************!*\ + !*** ../ckeditor5-inspector/src/view/data/utils.js ***! + \*****************************************************/ +/*! exports provided: DOCS_URL_PREFIX, getEditorViewRoots, getEditorViewRanges, getEditorViewTreeDefinition, getEditorViewNodeDefinition */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"DOCS_URL_PREFIX\", function() { return DOCS_URL_PREFIX; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorViewRoots\", function() { return getEditorViewRoots; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorViewRanges\", function() { return getEditorViewRanges; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorViewTreeDefinition\", function() { return getEditorViewTreeDefinition; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getEditorViewNodeDefinition\", function() { return getEditorViewNodeDefinition; });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../utils */ \"../ckeditor5-inspector/src/view/utils.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils */ \"../ckeditor5-inspector/src/utils.js\");\n/* harmony import */ var _components_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../components/utils */ \"../ckeditor5-inspector/src/components/utils.js\");\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\n\n\n\nconst DOCS_URL_PREFIX = 'https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_view';\nconst UI_ELEMENT_CONTENT_COMMENT = '<!--' + 'The View UI element content has been skipped. ' + `
        Find out why.` + ' -->';\nconst RAW_ELEMENT_CONTENT_COMMENT = '<!--' + 'The View raw element content has been skipped. ' + `Find out why.` + ' -->';\nfunction getEditorViewRoots(editor) {\n if (!editor) {\n return [];\n }\n\n return [...editor.editing.view.document.roots];\n}\nfunction getEditorViewRanges(editor, currentRootName) {\n if (!editor) {\n return [];\n }\n\n const ranges = [];\n const selection = editor.editing.view.document.selection;\n\n for (const range of selection.getRanges()) {\n if (range.root.rootName !== currentRootName) {\n continue;\n }\n\n ranges.push({\n type: 'selection',\n start: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getViewPositionDefinition\"])(range.start),\n end: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"getViewPositionDefinition\"])(range.end)\n });\n }\n\n return ranges;\n}\nfunction getEditorViewTreeDefinition({\n currentEditor,\n currentRootName,\n ranges\n}) {\n if (!currentEditor || !currentRootName) {\n return null;\n }\n\n const document = currentEditor.editing.view.document;\n const root = document.getRoot(currentRootName);\n return [getViewNodeDefinition(root, [...ranges])];\n}\nfunction getEditorViewNodeDefinition(node) {\n const info = {\n editorNode: node,\n properties: {},\n attributes: {},\n customProperties: {}\n };\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewElement\"])(node)) {\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewRoot\"])(node)) {\n info.type = 'RootEditableElement';\n info.name = node.rootName;\n info.url = `${DOCS_URL_PREFIX}_rooteditableelement-RootEditableElement.html`;\n } else {\n info.name = node.name;\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewAttributeElement\"])(node)) {\n info.type = 'AttributeElement';\n info.url = `${DOCS_URL_PREFIX}_attributeelement-AttributeElement.html`;\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewEmptyElement\"])(node)) {\n info.type = 'EmptyElement';\n info.url = `${DOCS_URL_PREFIX}_emptyelement-EmptyElement.html`;\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewUiElement\"])(node)) {\n info.type = 'UIElement';\n info.url = `${DOCS_URL_PREFIX}_uielement-UIElement.html`;\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewRawElement\"])(node)) {\n info.type = 'RawElement';\n info.url = `${DOCS_URL_PREFIX}_rawelement-RawElement.html`;\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewEditableElement\"])(node)) {\n info.type = 'EditableElement';\n info.url = `${DOCS_URL_PREFIX}_editableelement-EditableElement.html`;\n } else {\n info.type = 'ContainerElement';\n info.url = `${DOCS_URL_PREFIX}_containerelement-ContainerElement.html`;\n }\n }\n\n getSortedNodeAttributes(node).forEach(([name, value]) => {\n info.attributes[name] = {\n value\n };\n });\n info.properties = {\n index: {\n value: node.index\n },\n isEmpty: {\n value: node.isEmpty\n },\n childCount: {\n value: node.childCount\n }\n };\n\n for (let [name, value] of node.getCustomProperties()) {\n if (typeof name === 'symbol') {\n name = name.toString();\n }\n\n info.customProperties[name] = {\n value\n };\n }\n } else {\n info.name = node.data;\n info.type = 'Text';\n info.url = `${DOCS_URL_PREFIX}_text-Text.html`;\n info.properties = {\n index: {\n value: node.index\n }\n };\n }\n\n info.properties = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(info.properties);\n info.customProperties = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(info.customProperties);\n info.attributes = Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringifyPropertyList\"])(info.attributes);\n return info;\n}\n\nfunction getViewNodeDefinition(node, ranges) {\n const nodeDefinition = {};\n Object.assign(nodeDefinition, {\n index: node.index,\n path: node.getPath(),\n node,\n positionsBefore: [],\n positionsAfter: []\n });\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewElement\"])(node)) {\n fillElementDefinition(nodeDefinition, ranges);\n } else {\n fillViewTextNodeDefinition(nodeDefinition, ranges);\n }\n\n return nodeDefinition;\n}\n\nfunction fillElementDefinition(elementDefinition, ranges) {\n const element = elementDefinition.node;\n Object.assign(elementDefinition, {\n type: 'element',\n children: [],\n positions: []\n });\n elementDefinition.name = element.name;\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewAttributeElement\"])(element)) {\n elementDefinition.elementType = 'attribute';\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewRoot\"])(element)) {\n elementDefinition.elementType = 'root';\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewEmptyElement\"])(element)) {\n elementDefinition.elementType = 'empty';\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewUiElement\"])(element)) {\n elementDefinition.elementType = 'ui';\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewRawElement\"])(element)) {\n elementDefinition.elementType = 'raw';\n } else {\n elementDefinition.elementType = 'container';\n } // Regardless of other rendering options, empty elements need no closing tags. They will never\n // host any children or selection.\n\n\n if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewEmptyElement\"])(element)) {\n elementDefinition.presentation = {\n isEmpty: true\n };\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewUiElement\"])(element)) {\n elementDefinition.children.push({\n type: 'comment',\n text: UI_ELEMENT_CONTENT_COMMENT\n });\n } else if (Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\"isViewRawElement\"])(element)) {\n elementDefinition.children.push({\n type: 'comment',\n text: RAW_ELEMENT_CONTENT_COMMENT\n });\n }\n\n for (const child of element.getChildren()) {\n elementDefinition.children.push(getViewNodeDefinition(child, ranges));\n }\n\n fillViewElementDefinitionPositions(elementDefinition, ranges);\n elementDefinition.attributes = getNodeAttributesForDefinition(element);\n}\n\nfunction fillViewTextNodeDefinition(textNodeDefinition, ranges) {\n Object.assign(textNodeDefinition, {\n type: 'text',\n startOffset: 0,\n text: textNodeDefinition.node.data,\n positions: []\n });\n\n for (const range of ranges) {\n const positions = getRangePositionsInViewNode(textNodeDefinition, range);\n textNodeDefinition.positions.push(...positions);\n }\n}\n\nfunction fillViewElementDefinitionPositions(elementDefinition, ranges) {\n for (const range of ranges) {\n const positions = getRangePositionsInViewNode(elementDefinition, range);\n\n for (const position of positions) {\n const offset = position.offset;\n\n if (offset === 0) {\n const firstChild = elementDefinition.children[0];\n\n if (firstChild) {\n firstChild.positionsBefore.push(position);\n } else {\n elementDefinition.positions.push(position);\n }\n } else if (offset === elementDefinition.children.length) {\n const lastChild = elementDefinition.children[elementDefinition.children.length - 1];\n\n if (lastChild) {\n lastChild.positionsAfter.push(position);\n } else {\n elementDefinition.positions.push(position);\n }\n } else {\n // Go backward when looking for a child that will host the end position.\n // Go forward when looking for a child that will host the start position.\n //\n //\t\t

        \n //\t\t[

        ]\n //\t\t

        \n //\n // instead of\n //\n //\t\t

        [\n //\t\t

        \n //\t\t]

        \n //\n let childIndex = position.isEnd ? 0 : elementDefinition.children.length - 1;\n let child = elementDefinition.children[childIndex];\n\n while (child) {\n if (child.index === offset) {\n child.positionsBefore.push(position);\n break;\n }\n\n if (child.index + 1 === offset) {\n child.positionsAfter.push(position);\n break;\n }\n\n childIndex += position.isEnd ? 1 : -1;\n child = elementDefinition.children[childIndex];\n }\n }\n }\n }\n}\n\nfunction getRangePositionsInViewNode(nodeDefinition, range) {\n const nodePath = nodeDefinition.path;\n const startPath = range.start.path;\n const endPath = range.end.path;\n const positions = [];\n\n if (isPathPrefixingAnother(nodePath, startPath)) {\n positions.push({\n offset: startPath[startPath.length - 1],\n isEnd: false,\n presentation: range.presentation || null,\n type: range.type,\n name: range.name || null\n });\n }\n\n if (isPathPrefixingAnother(nodePath, endPath)) {\n positions.push({\n offset: endPath[endPath.length - 1],\n isEnd: true,\n presentation: range.presentation || null,\n type: range.type,\n name: range.name || null\n });\n }\n\n return positions;\n}\n\nfunction isPathPrefixingAnother(pathA, pathB) {\n if (pathA.length === pathB.length - 1) {\n const comparison = Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\"compareArrays\"])(pathA, pathB);\n\n if (comparison === 'prefix') {\n return true;\n }\n }\n\n return false;\n}\n\nfunction getNodeAttributesForDefinition(node) {\n const attrs = getSortedNodeAttributes(node).map(([name, value]) => {\n return [name, Object(_components_utils__WEBPACK_IMPORTED_MODULE_2__[\"stringify\"])(value, false)];\n });\n return new Map(attrs);\n}\n\nfunction getSortedNodeAttributes(node) {\n return [...node.getAttributes()].sort(([nameA], [nameB]) => nameA.toUpperCase() < nameB.toUpperCase() ? -1 : 1);\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/view/data/utils.js?"); + +/***/ }), + +/***/ "../ckeditor5-inspector/src/view/utils.js": +/*!************************************************!*\ + !*** ../ckeditor5-inspector/src/view/utils.js ***! + \************************************************/ +/*! exports provided: isViewElement, isViewAttributeElement, isViewEmptyElement, isViewUiElement, isViewRawElement, isViewEditableElement, isViewRoot, getViewPositionDefinition, nodeToString */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewElement\", function() { return isViewElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewAttributeElement\", function() { return isViewAttributeElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewEmptyElement\", function() { return isViewEmptyElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewUiElement\", function() { return isViewUiElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewRawElement\", function() { return isViewRawElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewEditableElement\", function() { return isViewEditableElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"isViewRoot\", function() { return isViewRoot; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getViewPositionDefinition\", function() { return getViewPositionDefinition; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"nodeToString\", function() { return nodeToString; });\n/**\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md.\n */\nfunction isViewElement(node) {\n return node && node.name;\n}\nfunction isViewAttributeElement(node) {\n return node && isViewElement(node) && node.is('attributeElement');\n}\nfunction isViewEmptyElement(node) {\n return node && isViewElement(node) && node.is('emptyElement');\n}\nfunction isViewUiElement(node) {\n return node && isViewElement(node) && node.is('uiElement');\n}\nfunction isViewRawElement(node) {\n return node && isViewElement(node) && node.is('rawElement');\n}\nfunction isViewEditableElement(node) {\n return node && isViewElement(node) && node.is('editableElement');\n}\nfunction isViewRoot(node) {\n return node && node.is('rootElement');\n}\nfunction getViewPositionDefinition(position) {\n return {\n path: [...position.parent.getPath(), position.offset],\n offset: position.offset,\n isAtEnd: position.isAtEnd,\n isAtStart: position.isAtStart,\n parent: nodeToString(position.parent)\n };\n}\nfunction nodeToString(node) {\n if (isViewElement(node)) {\n if (isViewAttributeElement(node)) {\n return 'attribute:' + node.name;\n } else if (isViewRoot(node)) {\n return 'root:' + node.name;\n } else {\n return 'container:' + node.name;\n }\n } else {\n return node.data;\n }\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/view/utils.js?"); + +/***/ }), + +/***/ "./node_modules/base64-js/index.js": +/*!*****************************************!*\ + !*** ./node_modules/base64-js/index.js ***! + \*****************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/base64-js/index.js?"); + +/***/ }), + +/***/ "./node_modules/buffer/index.js": +/*!**************************************!*\ + !*** ./node_modules/buffer/index.js ***! + \**************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/* WEBPACK VAR INJECTION */(function(global) {/*!\n * The buffer module from node.js, for the browser.\n *\n * @author Feross Aboukhadijeh \n * @license MIT\n */\n/* eslint-disable no-proto */\n\n\n\nvar base64 = __webpack_require__(/*! base64-js */ \"./node_modules/base64-js/index.js\")\nvar ieee754 = __webpack_require__(/*! ieee754 */ \"./node_modules/ieee754/index.js\")\nvar isArray = __webpack_require__(/*! isarray */ \"./node_modules/isarray/index.js\")\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n * === true Use Uint8Array implementation (fastest)\n * === false Use Object implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * Due to various browser bugs, sometimes the Object implementation will be used even\n * when the browser supports typed arrays.\n *\n * Note:\n *\n * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,\n * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.\n *\n * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.\n *\n * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of\n * incorrect length in some situations.\n\n * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they\n * get the Object implementation, which is slower but behaves correctly.\n */\nBuffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined\n ? global.TYPED_ARRAY_SUPPORT\n : typedArraySupport()\n\n/*\n * Export kMaxLength after typed array support is determined.\n */\nexports.kMaxLength = kMaxLength()\n\nfunction typedArraySupport () {\n try {\n var arr = new Uint8Array(1)\n arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}\n return arr.foo() === 42 && // typed array instances can be augmented\n typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`\n arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`\n } catch (e) {\n return false\n }\n}\n\nfunction kMaxLength () {\n return Buffer.TYPED_ARRAY_SUPPORT\n ? 0x7fffffff\n : 0x3fffffff\n}\n\nfunction createBuffer (that, length) {\n if (kMaxLength() < length) {\n throw new RangeError('Invalid typed array length')\n }\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = new Uint8Array(length)\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n if (that === null) {\n that = new Buffer(length)\n }\n that.length = length\n }\n\n return that\n}\n\n/**\n * The Buffer constructor returns instances of `Uint8Array` that have their\n * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of\n * `Uint8Array`, so the returned instances will have all the node `Buffer` methods\n * and the `Uint8Array` methods. Square bracket notation works as expected -- it\n * returns a single octet.\n *\n * The `Uint8Array` prototype remains unmodified.\n */\n\nfunction Buffer (arg, encodingOrOffset, length) {\n if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {\n return new Buffer(arg, encodingOrOffset, length)\n }\n\n // Common case.\n if (typeof arg === 'number') {\n if (typeof encodingOrOffset === 'string') {\n throw new Error(\n 'If encoding is specified then the first argument must be a string'\n )\n }\n return allocUnsafe(this, arg)\n }\n return from(this, arg, encodingOrOffset, length)\n}\n\nBuffer.poolSize = 8192 // not used by this implementation\n\n// TODO: Legacy, not needed anymore. Remove in next major version.\nBuffer._augment = function (arr) {\n arr.__proto__ = Buffer.prototype\n return arr\n}\n\nfunction from (that, value, encodingOrOffset, length) {\n if (typeof value === 'number') {\n throw new TypeError('\"value\" argument must not be a number')\n }\n\n if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {\n return fromArrayBuffer(that, value, encodingOrOffset, length)\n }\n\n if (typeof value === 'string') {\n return fromString(that, value, encodingOrOffset)\n }\n\n return fromObject(that, value)\n}\n\n/**\n * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError\n * if value is a number.\n * Buffer.from(str[, encoding])\n * Buffer.from(array)\n * Buffer.from(buffer)\n * Buffer.from(arrayBuffer[, byteOffset[, length]])\n **/\nBuffer.from = function (value, encodingOrOffset, length) {\n return from(null, value, encodingOrOffset, length)\n}\n\nif (Buffer.TYPED_ARRAY_SUPPORT) {\n Buffer.prototype.__proto__ = Uint8Array.prototype\n Buffer.__proto__ = Uint8Array\n if (typeof Symbol !== 'undefined' && Symbol.species &&\n Buffer[Symbol.species] === Buffer) {\n // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97\n Object.defineProperty(Buffer, Symbol.species, {\n value: null,\n configurable: true\n })\n }\n}\n\nfunction assertSize (size) {\n if (typeof size !== 'number') {\n throw new TypeError('\"size\" argument must be a number')\n } else if (size < 0) {\n throw new RangeError('\"size\" argument must not be negative')\n }\n}\n\nfunction alloc (that, size, fill, encoding) {\n assertSize(size)\n if (size <= 0) {\n return createBuffer(that, size)\n }\n if (fill !== undefined) {\n // Only pay attention to encoding if it's a string. This\n // prevents accidentally sending in a number that would\n // be interpretted as a start offset.\n return typeof encoding === 'string'\n ? createBuffer(that, size).fill(fill, encoding)\n : createBuffer(that, size).fill(fill)\n }\n return createBuffer(that, size)\n}\n\n/**\n * Creates a new filled Buffer instance.\n * alloc(size[, fill[, encoding]])\n **/\nBuffer.alloc = function (size, fill, encoding) {\n return alloc(null, size, fill, encoding)\n}\n\nfunction allocUnsafe (that, size) {\n assertSize(size)\n that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) {\n for (var i = 0; i < size; ++i) {\n that[i] = 0\n }\n }\n return that\n}\n\n/**\n * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.\n * */\nBuffer.allocUnsafe = function (size) {\n return allocUnsafe(null, size)\n}\n/**\n * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.\n */\nBuffer.allocUnsafeSlow = function (size) {\n return allocUnsafe(null, size)\n}\n\nfunction fromString (that, string, encoding) {\n if (typeof encoding !== 'string' || encoding === '') {\n encoding = 'utf8'\n }\n\n if (!Buffer.isEncoding(encoding)) {\n throw new TypeError('\"encoding\" must be a valid string encoding')\n }\n\n var length = byteLength(string, encoding) | 0\n that = createBuffer(that, length)\n\n var actual = that.write(string, encoding)\n\n if (actual !== length) {\n // Writing a hex string, for example, that contains invalid characters will\n // cause everything after the first invalid character to be ignored. (e.g.\n // 'abxxcd' will be treated as 'ab')\n that = that.slice(0, actual)\n }\n\n return that\n}\n\nfunction fromArrayLike (that, array) {\n var length = array.length < 0 ? 0 : checked(array.length) | 0\n that = createBuffer(that, length)\n for (var i = 0; i < length; i += 1) {\n that[i] = array[i] & 255\n }\n return that\n}\n\nfunction fromArrayBuffer (that, array, byteOffset, length) {\n array.byteLength // this throws if `array` is not a valid ArrayBuffer\n\n if (byteOffset < 0 || array.byteLength < byteOffset) {\n throw new RangeError('\\'offset\\' is out of bounds')\n }\n\n if (array.byteLength < byteOffset + (length || 0)) {\n throw new RangeError('\\'length\\' is out of bounds')\n }\n\n if (byteOffset === undefined && length === undefined) {\n array = new Uint8Array(array)\n } else if (length === undefined) {\n array = new Uint8Array(array, byteOffset)\n } else {\n array = new Uint8Array(array, byteOffset, length)\n }\n\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = array\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n that = fromArrayLike(that, array)\n }\n return that\n}\n\nfunction fromObject (that, obj) {\n if (Buffer.isBuffer(obj)) {\n var len = checked(obj.length) | 0\n that = createBuffer(that, len)\n\n if (that.length === 0) {\n return that\n }\n\n obj.copy(that, 0, 0, len)\n return that\n }\n\n if (obj) {\n if ((typeof ArrayBuffer !== 'undefined' &&\n obj.buffer instanceof ArrayBuffer) || 'length' in obj) {\n if (typeof obj.length !== 'number' || isnan(obj.length)) {\n return createBuffer(that, 0)\n }\n return fromArrayLike(that, obj)\n }\n\n if (obj.type === 'Buffer' && isArray(obj.data)) {\n return fromArrayLike(that, obj.data)\n }\n }\n\n throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')\n}\n\nfunction checked (length) {\n // Note: cannot use `length < kMaxLength()` here because that fails when\n // length is NaN (which is otherwise coerced to zero.)\n if (length >= kMaxLength()) {\n throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n 'size: 0x' + kMaxLength().toString(16) + ' bytes')\n }\n return length | 0\n}\n\nfunction SlowBuffer (length) {\n if (+length != length) { // eslint-disable-line eqeqeq\n length = 0\n }\n return Buffer.alloc(+length)\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n return !!(b != null && b._isBuffer)\n}\n\nBuffer.compare = function compare (a, b) {\n if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n throw new TypeError('Arguments must be Buffers')\n }\n\n if (a === b) return 0\n\n var x = a.length\n var y = b.length\n\n for (var i = 0, len = Math.min(x, y); i < len; ++i) {\n if (a[i] !== b[i]) {\n x = a[i]\n y = b[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n switch (String(encoding).toLowerCase()) {\n case 'hex':\n case 'utf8':\n case 'utf-8':\n case 'ascii':\n case 'latin1':\n case 'binary':\n case 'base64':\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return true\n default:\n return false\n }\n}\n\nBuffer.concat = function concat (list, length) {\n if (!isArray(list)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n\n if (list.length === 0) {\n return Buffer.alloc(0)\n }\n\n var i\n if (length === undefined) {\n length = 0\n for (i = 0; i < list.length; ++i) {\n length += list[i].length\n }\n }\n\n var buffer = Buffer.allocUnsafe(length)\n var pos = 0\n for (i = 0; i < list.length; ++i) {\n var buf = list[i]\n if (!Buffer.isBuffer(buf)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n buf.copy(buffer, pos)\n pos += buf.length\n }\n return buffer\n}\n\nfunction byteLength (string, encoding) {\n if (Buffer.isBuffer(string)) {\n return string.length\n }\n if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&\n (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {\n return string.byteLength\n }\n if (typeof string !== 'string') {\n string = '' + string\n }\n\n var len = string.length\n if (len === 0) return 0\n\n // Use a for loop to avoid recursion\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'ascii':\n case 'latin1':\n case 'binary':\n return len\n case 'utf8':\n case 'utf-8':\n case undefined:\n return utf8ToBytes(string).length\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return len * 2\n case 'hex':\n return len >>> 1\n case 'base64':\n return base64ToBytes(string).length\n default:\n if (loweredCase) return utf8ToBytes(string).length // assume utf8\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\nBuffer.byteLength = byteLength\n\nfunction slowToString (encoding, start, end) {\n var loweredCase = false\n\n // No need to verify that \"this.length <= MAX_UINT32\" since it's a read-only\n // property of a typed array.\n\n // This behaves neither like String nor Uint8Array in that we set start/end\n // to their upper/lower bounds if the value passed is out of range.\n // undefined is handled specially as per ECMA-262 6th Edition,\n // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.\n if (start === undefined || start < 0) {\n start = 0\n }\n // Return early if start > this.length. Done here to prevent potential uint32\n // coercion fail below.\n if (start > this.length) {\n return ''\n }\n\n if (end === undefined || end > this.length) {\n end = this.length\n }\n\n if (end <= 0) {\n return ''\n }\n\n // Force coersion to uint32. This will also coerce falsey/NaN values to 0.\n end >>>= 0\n start >>>= 0\n\n if (end <= start) {\n return ''\n }\n\n if (!encoding) encoding = 'utf8'\n\n while (true) {\n switch (encoding) {\n case 'hex':\n return hexSlice(this, start, end)\n\n case 'utf8':\n case 'utf-8':\n return utf8Slice(this, start, end)\n\n case 'ascii':\n return asciiSlice(this, start, end)\n\n case 'latin1':\n case 'binary':\n return latin1Slice(this, start, end)\n\n case 'base64':\n return base64Slice(this, start, end)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return utf16leSlice(this, start, end)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = (encoding + '').toLowerCase()\n loweredCase = true\n }\n }\n}\n\n// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect\n// Buffer instances.\nBuffer.prototype._isBuffer = true\n\nfunction swap (b, n, m) {\n var i = b[n]\n b[n] = b[m]\n b[m] = i\n}\n\nBuffer.prototype.swap16 = function swap16 () {\n var len = this.length\n if (len % 2 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 16-bits')\n }\n for (var i = 0; i < len; i += 2) {\n swap(this, i, i + 1)\n }\n return this\n}\n\nBuffer.prototype.swap32 = function swap32 () {\n var len = this.length\n if (len % 4 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 32-bits')\n }\n for (var i = 0; i < len; i += 4) {\n swap(this, i, i + 3)\n swap(this, i + 1, i + 2)\n }\n return this\n}\n\nBuffer.prototype.swap64 = function swap64 () {\n var len = this.length\n if (len % 8 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 64-bits')\n }\n for (var i = 0; i < len; i += 8) {\n swap(this, i, i + 7)\n swap(this, i + 1, i + 6)\n swap(this, i + 2, i + 5)\n swap(this, i + 3, i + 4)\n }\n return this\n}\n\nBuffer.prototype.toString = function toString () {\n var length = this.length | 0\n if (length === 0) return ''\n if (arguments.length === 0) return utf8Slice(this, 0, length)\n return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.equals = function equals (b) {\n if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n if (this === b) return true\n return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n var str = ''\n var max = exports.INSPECT_MAX_BYTES\n if (this.length > 0) {\n str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')\n if (this.length > max) str += ' ... '\n }\n return ''\n}\n\nBuffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {\n if (!Buffer.isBuffer(target)) {\n throw new TypeError('Argument must be a Buffer')\n }\n\n if (start === undefined) {\n start = 0\n }\n if (end === undefined) {\n end = target ? target.length : 0\n }\n if (thisStart === undefined) {\n thisStart = 0\n }\n if (thisEnd === undefined) {\n thisEnd = this.length\n }\n\n if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {\n throw new RangeError('out of range index')\n }\n\n if (thisStart >= thisEnd && start >= end) {\n return 0\n }\n if (thisStart >= thisEnd) {\n return -1\n }\n if (start >= end) {\n return 1\n }\n\n start >>>= 0\n end >>>= 0\n thisStart >>>= 0\n thisEnd >>>= 0\n\n if (this === target) return 0\n\n var x = thisEnd - thisStart\n var y = end - start\n var len = Math.min(x, y)\n\n var thisCopy = this.slice(thisStart, thisEnd)\n var targetCopy = target.slice(start, end)\n\n for (var i = 0; i < len; ++i) {\n if (thisCopy[i] !== targetCopy[i]) {\n x = thisCopy[i]\n y = targetCopy[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant is val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {\n // Empty buffer means no match\n if (buffer.length === 0) return -1\n\n // Normalize byteOffset\n if (typeof byteOffset === 'string') {\n encoding = byteOffset\n byteOffset = 0\n } else if (byteOffset > 0x7fffffff) {\n byteOffset = 0x7fffffff\n } else if (byteOffset < -0x80000000) {\n byteOffset = -0x80000000\n }\n byteOffset = +byteOffset // Coerce to Number.\n if (isNaN(byteOffset)) {\n // byteOffset: it it's undefined, null, NaN, \"foo\", etc, search whole buffer\n byteOffset = dir ? 0 : (buffer.length - 1)\n }\n\n // Normalize byteOffset: negative offsets start from the end of the buffer\n if (byteOffset < 0) byteOffset = buffer.length + byteOffset\n if (byteOffset >= buffer.length) {\n if (dir) return -1\n else byteOffset = buffer.length - 1\n } else if (byteOffset < 0) {\n if (dir) byteOffset = 0\n else return -1\n }\n\n // Normalize val\n if (typeof val === 'string') {\n val = Buffer.from(val, encoding)\n }\n\n // Finally, search either indexOf (if dir is true) or lastIndexOf\n if (Buffer.isBuffer(val)) {\n // Special case: looking for empty string/buffer always fails\n if (val.length === 0) {\n return -1\n }\n return arrayIndexOf(buffer, val, byteOffset, encoding, dir)\n } else if (typeof val === 'number') {\n val = val & 0xFF // Search for a byte value [0-255]\n if (Buffer.TYPED_ARRAY_SUPPORT &&\n typeof Uint8Array.prototype.indexOf === 'function') {\n if (dir) {\n return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)\n } else {\n return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)\n }\n }\n return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)\n }\n\n throw new TypeError('val must be string, number or Buffer')\n}\n\nfunction arrayIndexOf (arr, val, byteOffset, encoding, dir) {\n var indexSize = 1\n var arrLength = arr.length\n var valLength = val.length\n\n if (encoding !== undefined) {\n encoding = String(encoding).toLowerCase()\n if (encoding === 'ucs2' || encoding === 'ucs-2' ||\n encoding === 'utf16le' || encoding === 'utf-16le') {\n if (arr.length < 2 || val.length < 2) {\n return -1\n }\n indexSize = 2\n arrLength /= 2\n valLength /= 2\n byteOffset /= 2\n }\n }\n\n function read (buf, i) {\n if (indexSize === 1) {\n return buf[i]\n } else {\n return buf.readUInt16BE(i * indexSize)\n }\n }\n\n var i\n if (dir) {\n var foundIndex = -1\n for (i = byteOffset; i < arrLength; i++) {\n if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {\n if (foundIndex === -1) foundIndex = i\n if (i - foundIndex + 1 === valLength) return foundIndex * indexSize\n } else {\n if (foundIndex !== -1) i -= i - foundIndex\n foundIndex = -1\n }\n }\n } else {\n if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength\n for (i = byteOffset; i >= 0; i--) {\n var found = true\n for (var j = 0; j < valLength; j++) {\n if (read(arr, i + j) !== read(val, j)) {\n found = false\n break\n }\n }\n if (found) return i\n }\n }\n\n return -1\n}\n\nBuffer.prototype.includes = function includes (val, byteOffset, encoding) {\n return this.indexOf(val, byteOffset, encoding) !== -1\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, true)\n}\n\nBuffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, false)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n offset = Number(offset) || 0\n var remaining = buf.length - offset\n if (!length) {\n length = remaining\n } else {\n length = Number(length)\n if (length > remaining) {\n length = remaining\n }\n }\n\n // must be an even number of digits\n var strLen = string.length\n if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')\n\n if (length > strLen / 2) {\n length = strLen / 2\n }\n for (var i = 0; i < length; ++i) {\n var parsed = parseInt(string.substr(i * 2, 2), 16)\n if (isNaN(parsed)) return i\n buf[offset + i] = parsed\n }\n return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction latin1Write (buf, string, offset, length) {\n return asciiWrite(buf, string, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n // Buffer#write(string)\n if (offset === undefined) {\n encoding = 'utf8'\n length = this.length\n offset = 0\n // Buffer#write(string, encoding)\n } else if (length === undefined && typeof offset === 'string') {\n encoding = offset\n length = this.length\n offset = 0\n // Buffer#write(string, offset[, length][, encoding])\n } else if (isFinite(offset)) {\n offset = offset | 0\n if (isFinite(length)) {\n length = length | 0\n if (encoding === undefined) encoding = 'utf8'\n } else {\n encoding = length\n length = undefined\n }\n // legacy write(string, encoding, offset, length) - remove in v0.13\n } else {\n throw new Error(\n 'Buffer.write(string, encoding, offset[, length]) is no longer supported'\n )\n }\n\n var remaining = this.length - offset\n if (length === undefined || length > remaining) length = remaining\n\n if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n throw new RangeError('Attempt to write outside buffer bounds')\n }\n\n if (!encoding) encoding = 'utf8'\n\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'hex':\n return hexWrite(this, string, offset, length)\n\n case 'utf8':\n case 'utf-8':\n return utf8Write(this, string, offset, length)\n\n case 'ascii':\n return asciiWrite(this, string, offset, length)\n\n case 'latin1':\n case 'binary':\n return latin1Write(this, string, offset, length)\n\n case 'base64':\n // Warning: maxLength not taken into account in base64Write\n return base64Write(this, string, offset, length)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return ucs2Write(this, string, offset, length)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n return {\n type: 'Buffer',\n data: Array.prototype.slice.call(this._arr || this, 0)\n }\n}\n\nfunction base64Slice (buf, start, end) {\n if (start === 0 && end === buf.length) {\n return base64.fromByteArray(buf)\n } else {\n return base64.fromByteArray(buf.slice(start, end))\n }\n}\n\nfunction utf8Slice (buf, start, end) {\n end = Math.min(buf.length, end)\n var res = []\n\n var i = start\n while (i < end) {\n var firstByte = buf[i]\n var codePoint = null\n var bytesPerSequence = (firstByte > 0xEF) ? 4\n : (firstByte > 0xDF) ? 3\n : (firstByte > 0xBF) ? 2\n : 1\n\n if (i + bytesPerSequence <= end) {\n var secondByte, thirdByte, fourthByte, tempCodePoint\n\n switch (bytesPerSequence) {\n case 1:\n if (firstByte < 0x80) {\n codePoint = firstByte\n }\n break\n case 2:\n secondByte = buf[i + 1]\n if ((secondByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n if (tempCodePoint > 0x7F) {\n codePoint = tempCodePoint\n }\n }\n break\n case 3:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n codePoint = tempCodePoint\n }\n }\n break\n case 4:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n fourthByte = buf[i + 3]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n codePoint = tempCodePoint\n }\n }\n }\n }\n\n if (codePoint === null) {\n // we did not generate a valid codePoint so insert a\n // replacement char (U+FFFD) and advance only 1 byte\n codePoint = 0xFFFD\n bytesPerSequence = 1\n } else if (codePoint > 0xFFFF) {\n // encode to utf16 (surrogate pair dance)\n codePoint -= 0x10000\n res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n codePoint = 0xDC00 | codePoint & 0x3FF\n }\n\n res.push(codePoint)\n i += bytesPerSequence\n }\n\n return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nvar MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n var len = codePoints.length\n if (len <= MAX_ARGUMENTS_LENGTH) {\n return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n }\n\n // Decode in chunks to avoid \"call stack size exceeded\".\n var res = ''\n var i = 0\n while (i < len) {\n res += String.fromCharCode.apply(\n String,\n codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n )\n }\n return res\n}\n\nfunction asciiSlice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i] & 0x7F)\n }\n return ret\n}\n\nfunction latin1Slice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i])\n }\n return ret\n}\n\nfunction hexSlice (buf, start, end) {\n var len = buf.length\n\n if (!start || start < 0) start = 0\n if (!end || end < 0 || end > len) end = len\n\n var out = ''\n for (var i = start; i < end; ++i) {\n out += toHex(buf[i])\n }\n return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n var bytes = buf.slice(start, end)\n var res = ''\n for (var i = 0; i < bytes.length; i += 2) {\n res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)\n }\n return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n var len = this.length\n start = ~~start\n end = end === undefined ? len : ~~end\n\n if (start < 0) {\n start += len\n if (start < 0) start = 0\n } else if (start > len) {\n start = len\n }\n\n if (end < 0) {\n end += len\n if (end < 0) end = 0\n } else if (end > len) {\n end = len\n }\n\n if (end < start) end = start\n\n var newBuf\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n newBuf = this.subarray(start, end)\n newBuf.__proto__ = Buffer.prototype\n } else {\n var sliceLen = end - start\n newBuf = new Buffer(sliceLen, undefined)\n for (var i = 0; i < sliceLen; ++i) {\n newBuf[i] = this[i + start]\n }\n }\n\n return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n checkOffset(offset, byteLength, this.length)\n }\n\n var val = this[offset + --byteLength]\n var mul = 1\n while (byteLength > 0 && (mul *= 0x100)) {\n val += this[offset + --byteLength] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n return this[offset]\n}\n\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return ((this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16)) +\n (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] * 0x1000000) +\n ((this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n this[offset + 3])\n}\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var i = byteLength\n var mul = 1\n var val = this[offset + --i]\n while (i > 0 && (mul *= 0x100)) {\n val += this[offset + --i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n if (!(this[offset] & 0x80)) return (this[offset])\n return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset] | (this[offset + 1] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset + 1] | (this[offset] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16) |\n (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] << 24) |\n (this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n (this[offset + 3])\n}\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n if (!Buffer.isBuffer(buf)) throw new TypeError('\"buffer\" argument must be a Buffer instance')\n if (value > max || value < min) throw new RangeError('\"value\" argument is out of bounds')\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n}\n\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var mul = 1\n var i = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var i = byteLength - 1\n var mul = 1\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nfunction objectWriteUInt16 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {\n buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>\n (littleEndian ? i : 1 - i) * 8\n }\n}\n\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nfunction objectWriteUInt32 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffffffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {\n buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff\n }\n}\n\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset + 3] = (value >>> 24)\n this[offset + 2] = (value >>> 16)\n this[offset + 1] = (value >>> 8)\n this[offset] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = 0\n var mul = 1\n var sub = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = byteLength - 1\n var mul = 1\n var sub = 0\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n if (value < 0) value = 0xff + value + 1\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n this[offset + 2] = (value >>> 16)\n this[offset + 3] = (value >>> 24)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (value < 0) value = 0xffffffff + value + 1\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n if (offset < 0) throw new RangeError('Index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n }\n ieee754.write(buf, value, offset, littleEndian, 23, 4)\n return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n }\n ieee754.write(buf, value, offset, littleEndian, 52, 8)\n return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n if (!start) start = 0\n if (!end && end !== 0) end = this.length\n if (targetStart >= target.length) targetStart = target.length\n if (!targetStart) targetStart = 0\n if (end > 0 && end < start) end = start\n\n // Copy 0 bytes; we're done\n if (end === start) return 0\n if (target.length === 0 || this.length === 0) return 0\n\n // Fatal error conditions\n if (targetStart < 0) {\n throw new RangeError('targetStart out of bounds')\n }\n if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')\n if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n // Are we oob?\n if (end > this.length) end = this.length\n if (target.length - targetStart < end - start) {\n end = target.length - targetStart + start\n }\n\n var len = end - start\n var i\n\n if (this === target && start < targetStart && targetStart < end) {\n // descending copy from end\n for (i = len - 1; i >= 0; --i) {\n target[i + targetStart] = this[i + start]\n }\n } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {\n // ascending copy from start\n for (i = 0; i < len; ++i) {\n target[i + targetStart] = this[i + start]\n }\n } else {\n Uint8Array.prototype.set.call(\n target,\n this.subarray(start, start + len),\n targetStart\n )\n }\n\n return len\n}\n\n// Usage:\n// buffer.fill(number[, offset[, end]])\n// buffer.fill(buffer[, offset[, end]])\n// buffer.fill(string[, offset[, end]][, encoding])\nBuffer.prototype.fill = function fill (val, start, end, encoding) {\n // Handle string cases:\n if (typeof val === 'string') {\n if (typeof start === 'string') {\n encoding = start\n start = 0\n end = this.length\n } else if (typeof end === 'string') {\n encoding = end\n end = this.length\n }\n if (val.length === 1) {\n var code = val.charCodeAt(0)\n if (code < 256) {\n val = code\n }\n }\n if (encoding !== undefined && typeof encoding !== 'string') {\n throw new TypeError('encoding must be a string')\n }\n if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n } else if (typeof val === 'number') {\n val = val & 255\n }\n\n // Invalid ranges are not set to a default, so can range check early.\n if (start < 0 || this.length < start || this.length < end) {\n throw new RangeError('Out of range index')\n }\n\n if (end <= start) {\n return this\n }\n\n start = start >>> 0\n end = end === undefined ? this.length : end >>> 0\n\n if (!val) val = 0\n\n var i\n if (typeof val === 'number') {\n for (i = start; i < end; ++i) {\n this[i] = val\n }\n } else {\n var bytes = Buffer.isBuffer(val)\n ? val\n : utf8ToBytes(new Buffer(val, encoding).toString())\n var len = bytes.length\n for (i = 0; i < end - start; ++i) {\n this[i + start] = bytes[i % len]\n }\n }\n\n return this\n}\n\n// HELPER FUNCTIONS\n// ================\n\nvar INVALID_BASE64_RE = /[^+\\/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n str = stringtrim(str).replace(INVALID_BASE64_RE, '')\n // Node converts strings with length < 2 to ''\n if (str.length < 2) return ''\n // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n while (str.length % 4 !== 0) {\n str = str + '='\n }\n return str\n}\n\nfunction stringtrim (str) {\n if (str.trim) return str.trim()\n return str.replace(/^\\s+|\\s+$/g, '')\n}\n\nfunction toHex (n) {\n if (n < 16) return '0' + n.toString(16)\n return n.toString(16)\n}\n\nfunction utf8ToBytes (string, units) {\n units = units || Infinity\n var codePoint\n var length = string.length\n var leadSurrogate = null\n var bytes = []\n\n for (var i = 0; i < length; ++i) {\n codePoint = string.charCodeAt(i)\n\n // is surrogate component\n if (codePoint > 0xD7FF && codePoint < 0xE000) {\n // last char was a lead\n if (!leadSurrogate) {\n // no lead yet\n if (codePoint > 0xDBFF) {\n // unexpected trail\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n } else if (i + 1 === length) {\n // unpaired lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n }\n\n // valid lead\n leadSurrogate = codePoint\n\n continue\n }\n\n // 2 leads in a row\n if (codePoint < 0xDC00) {\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n leadSurrogate = codePoint\n continue\n }\n\n // valid surrogate pair\n codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000\n } else if (leadSurrogate) {\n // valid bmp char, but last char was a lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n }\n\n leadSurrogate = null\n\n // encode utf8\n if (codePoint < 0x80) {\n if ((units -= 1) < 0) break\n bytes.push(codePoint)\n } else if (codePoint < 0x800) {\n if ((units -= 2) < 0) break\n bytes.push(\n codePoint >> 0x6 | 0xC0,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x10000) {\n if ((units -= 3) < 0) break\n bytes.push(\n codePoint >> 0xC | 0xE0,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x110000) {\n if ((units -= 4) < 0) break\n bytes.push(\n codePoint >> 0x12 | 0xF0,\n codePoint >> 0xC & 0x3F | 0x80,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else {\n throw new Error('Invalid code point')\n }\n }\n\n return bytes\n}\n\nfunction asciiToBytes (str) {\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n // Node's code seems to be doing this and not & 0x7F..\n byteArray.push(str.charCodeAt(i) & 0xFF)\n }\n return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n var c, hi, lo\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n if ((units -= 2) < 0) break\n\n c = str.charCodeAt(i)\n hi = c >> 8\n lo = c % 256\n byteArray.push(lo)\n byteArray.push(hi)\n }\n\n return byteArray\n}\n\nfunction base64ToBytes (str) {\n return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n for (var i = 0; i < length; ++i) {\n if ((i + offset >= dst.length) || (i >= src.length)) break\n dst[i + offset] = src[i]\n }\n return i\n}\n\nfunction isnan (val) {\n return val !== val // eslint-disable-line no-self-compare\n}\n\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ \"./node_modules/webpack/buildin/global.js\")))\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/buffer/index.js?"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!../ckeditor5-inspector/src/components/tree/tree.css": +/*!*******************************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!../ckeditor5-inspector/src/components/tree/tree.css ***! + \*******************************************************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("exports = module.exports = __webpack_require__(/*! ../../../../ckeditor5-mini-inspector/node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\")(true);\n// Module\nexports.push([module.i, \"/**\\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\\n * For licensing, see LICENSE.md.\\n */\\n\\n.ck-inspector {\\n\\t--ck-inspector-color-tree-node-hover: #EAF2FB;\\n\\t--ck-inspector-color-tree-node-name: #882680;\\n\\t--ck-inspector-color-tree-node-attribute-name: #8a8a8a;\\n\\t--ck-inspector-color-tree-node-tag: #aaa;\\n\\t--ck-inspector-color-tree-node-attribute: #9A4819;\\n\\t--ck-inspector-color-tree-node-attribute-value: #2A43AC;\\n\\t--ck-inspector-color-tree-text-border: #b7b7b7;\\n\\t--ck-inspector-color-tree-node-border-hover: #b0c6e0;\\n\\t--ck-inspector-color-tree-content-delimiter: #ddd;\\n\\t--ck-inspector-color-tree-node-active-bg: #f5faff;\\n\\t--ck-inspector-color-tree-node-name-active-bg: #2B98F0;\\n\\t--ck-inspector-color-tree-node-inactive: #8a8a8a;\\n\\t--ck-inspector-color-tree-selection: #ff1744;\\n\\t--ck-inspector-color-tree-position: black;\\n\\t--ck-inspector-color-comment: green;\\n}\\n\\n.ck-inspector .ck-inspector-tree {\\n\\tbackground: var(--ck-inspector-color-white);\\n\\tpadding: 1em;\\n\\twidth: 100%;\\n\\theight: 100%;\\n\\toverflow: auto;\\n\\tuser-select: none;\\n}\\n\\n/*-- Attribute ---------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute {\\n\\tfont: inherit;\\n\\tmargin-left: .4em;\\n\\tcolor: var(--ck-inspector-color-tree-node-tag)\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute .ck-inspector-tree-node__attribute__name {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-attribute);\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute .ck-inspector-tree-node__attribute__value {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-attribute-value)\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute .ck-inspector-tree-node__attribute__value::before {\\n\\t\\t\\tcontent: '=\\\"'\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute .ck-inspector-tree-node__attribute__value::after {\\n\\t\\t\\tcontent: '\\\"'\\n\\t\\t}\\n\\n/*-- Node ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-node .ck-inspector-tree-node__name {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-name);\\n\\t\\tdisplay: inline-block;\\n\\t\\twidth: 100%;\\n\\t\\tpadding: 0 .1em;\\n\\t\\tborder-left: 1px solid transparent;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node .ck-inspector-tree-node__name:hover {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-hover);\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node .ck-inspector-tree-node__content {\\n\\t\\tpadding-left: 1.5em;\\n\\t\\tpadding-top: 1px;\\n\\t\\tpadding-bottom: 1px;\\n\\t\\tpadding-right: .5em;\\n\\t\\tborder-left: 1px solid var(--ck-inspector-color-tree-content-delimiter);\\n\\t\\t/* Allow multiple white spaces in a series to be displayed */\\n\\t\\twhite-space: pre-wrap;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node {\\n\\n\\t/*-- Tagged vs. tagless ------------------------------------------------------------------------*/\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-node:not(.ck-inspector-tree-node_tagless) .ck-inspector-tree-node__name > .ck-inspector-tree-node__name__bracket_open::after {\\n\\t\\t\\tcontent: \\\"<\\\";\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-tag);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node:not(.ck-inspector-tree-node_tagless) .ck-inspector-tree-node__name .ck-inspector-tree-node__name__bracket_close::after {\\n\\t\\t\\tcontent: \\\">\\\";\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-tag);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node:not(.ck-inspector-tree-node_tagless).ck-inspector-tree-node_empty .ck-inspector-tree-node__name::after {\\n\\t\\t\\tcontent: \\\" />\\\";\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_tagless .ck-inspector-tree-node__content {\\n\\t\\tdisplay: none;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node {\\n\\n\\t/*-- Active ------------------------------------------------------------------------*/\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__name:not(.ck-inspector-tree-node__name_close),\\n\\t\\t.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__name:not(.ck-inspector-tree-node__name_close) *:not(.ck-inspector-tree__position),\\n\\t\\t.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__name:not(.ck-inspector-tree-node__name_close) > .ck-inspector-tree-node__name__bracket::after {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__content,\\n\\t.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__name_close {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-active-bg);\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__content {\\n\\t\\tborder-left-color: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_active > .ck-inspector-tree-node__name {\\n\\t\\tborder-left: 1px solid var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node {\\n\\n\\t/*-- Disabled ------------------------------------------------------------------------*/\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_disabled {\\n\\t\\topacity: .8\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_disabled .ck-inspector-tree-node__name,\\n\\t\\t.ck-inspector-tree .ck-inspector-tree-node.ck-inspector-tree-node_disabled .ck-inspector-tree-node__name * {\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-inactive);\\n\\t\\t}\\n\\n/*-- Text ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-text {\\n\\tdisplay: block;\\n\\tmargin-bottom: 1px\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-node__content {\\n\\t\\tborder: 1px dotted var(--ck-inspector-color-tree-text-border);\\n\\t\\tborder-radius: 2px;\\n\\t\\tpadding: 0 1px;\\n\\t\\tmargin-right: 1px;\\n\\t\\tdisplay: inline-block;\\n\\t\\tword-break: break-all;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text {\\n\\n\\t/*-- Attributes ------------------------------------------------------------------------*/\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-text__attributes:not(:empty) {\\n\\t\\t\\tmargin-right: .5em;\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-text__attributes .ck-inspector-tree-node__attribute {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-attribute-name);\\n\\t\\t\\tborder-radius: 2px;\\n\\t\\t\\tpadding: 0 .5em\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-text__attributes .ck-inspector-tree-node__attribute + .ck-inspector-tree-node__attribute {\\n\\t\\t\\t\\tmargin-left: .2em;\\n\\t\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-text__attributes .ck-inspector-tree-node__attribute > * {\\n\\t\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text .ck-inspector-tree-text__attributes .ck-inspector-tree-node__attribute:first-child {\\n\\t\\t\\t\\tmargin-left: 0;\\n\\t\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text {\\n\\n\\t/*-- Active vs. inactive ------------------------------------------------------------------------*/\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-text.ck-inspector-tree-node_active .ck-inspector-tree-node__content {\\n\\t\\t\\tborder-style: solid;\\n\\t\\t\\tborder-color: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text.ck-inspector-tree-node_active .ck-inspector-tree-node__attribute {\\n\\t\\t\\tbackground: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text.ck-inspector-tree-node_active .ck-inspector-tree-node__attribute > * {\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text.ck-inspector-tree-node_active > .ck-inspector-tree-node__content {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree-text:not(.ck-inspector-tree-node_active) .ck-inspector-tree-node__content:hover {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-hover);\\n\\t\\tborder-style: solid;\\n\\t\\tborder-color: var(--ck-inspector-color-tree-node-border-hover);\\n\\t}\\n\\n/*-- LTR vs. RTL text ------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_ltr .ck-inspector-tree-node__content {\\n\\tdirection: ltr;\\n}\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_rtl .ck-inspector-tree-node__content {\\n\\tdirection: rtl\\n}\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_rtl .ck-inspector-tree-node__content .ck-inspector-tree-node__name {\\n\\t\\tdirection: ltr;\\n\\t}\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_rtl .ck-inspector-tree__position {\\n\\ttransform: rotate(180deg);\\n}\\n\\n\\n/*-- Comment -----------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree .ck-inspector-tree-comment {\\n\\tcolor: var(--ck-inspector-color-comment);\\n\\tfont-style: italic\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree-comment a {\\n\\t\\tcolor: inherit;\\n\\t\\ttext-decoration: underline;\\n\\t}\\n\\n/*-- Compact text ------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree_compact-text .ck-inspector-tree-text {\\n\\tdisplay: inline;\\n}\\n\\n.ck-inspector-tree_compact-text .ck-inspector-tree-text .ck-inspector-tree-node__content {\\n\\tdisplay: inline;\\n}\\n\\n\\n/*-- Navigation ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector .ck-inspector__tree__navigation {\\n\\tpadding: .5em 1em;\\n\\tborder-bottom: 1px solid var(--ck-inspector-color-border)\\n}\\n\\n.ck-inspector .ck-inspector__tree__navigation label {\\n\\t\\tmargin-right: .5em;\\n\\t}\\n\\n/*-- Selection ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree__position {\\n\\tdisplay: inline-block;\\n\\tposition: relative;\\n\\tcursor: default;\\n\\theight: 100%;\\n\\tpointer-events: none;\\n\\n\\t/* When nodes wrap to the new line */\\n\\tvertical-align: top\\n}\\n\\n.ck-inspector-tree .ck-inspector-tree__position::after {\\n\\t\\tcontent: \\\"\\\";\\n\\t\\tposition: absolute;\\n\\t\\tborder: 1px solid var(--ck-inspector-color-tree-position);\\n\\t\\twidth: 0;\\n\\t\\ttop: 0;\\n\\t\\tbottom: 0;\\n\\t\\tmargin-left: -1px;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position::before {\\n\\t\\tmargin-left: -1px;\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_selection {\\n\\t\\tz-index: 2;\\n\\t\\t--ck-inspector-color-tree-position: var(--ck-inspector-color-tree-selection)\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_selection::before {\\n\\t\\t\\tcontent: \\\"\\\";\\n\\t\\t\\tposition: absolute;\\n\\t\\t\\ttop: -1px;\\n\\t\\t\\tbottom: -1px;\\n\\t\\t\\tleft: 0;\\n\\t\\t\\tborder-top: 2px solid var(--ck-inspector-color-tree-position);\\n\\t\\t\\tborder-bottom: 2px solid var(--ck-inspector-color-tree-position);\\n\\t\\t\\twidth: 8px;\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_selection.ck-inspector-tree__position_end::before {\\n\\t\\t\\tright: -1px;\\n\\t\\t\\tleft: auto;\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_marker {\\n\\t\\tz-index: 1\\n\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_marker::before {\\n\\t\\t\\tcontent: \\\"\\\";\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\tposition: absolute;\\n\\t\\t\\tleft: 0;\\n\\t\\t\\ttop: -1px;\\n\\t\\t\\tcursor: default;\\n\\n\\t\\t\\twidth: 0;\\n\\t\\t\\theight: 0;\\n\\t\\t\\tborder-style: solid;\\n\\t\\t\\tborder-width: 7px 7px 0 0;\\n\\t\\t\\tborder-color: var(--ck-inspector-color-tree-position) transparent transparent transparent;\\n\\t\\t}\\n\\n.ck-inspector-tree .ck-inspector-tree__position.ck-inspector-tree__position_marker.ck-inspector-tree__position_end::before {\\n\\t\\t\\tborder-width: 0 7px 7px 0;\\n\\t\\t\\tborder-color: transparent var(--ck-inspector-color-tree-position) transparent transparent;\\n\\t\\t\\tleft: -5px;\\n\\t\\t}\\n\", \"\",{\"version\":3,\"sources\":[\"tree.css\"],\"names\":[],\"mappings\":\"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\",\"file\":\"tree.css\",\"sourcesContent\":[\"/**\\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\\n * For licensing, see LICENSE.md.\\n */\\n\\n.ck-inspector {\\n\\t--ck-inspector-color-tree-node-hover: #EAF2FB;\\n\\t--ck-inspector-color-tree-node-name: #882680;\\n\\t--ck-inspector-color-tree-node-attribute-name: #8a8a8a;\\n\\t--ck-inspector-color-tree-node-tag: #aaa;\\n\\t--ck-inspector-color-tree-node-attribute: #9A4819;\\n\\t--ck-inspector-color-tree-node-attribute-value: #2A43AC;\\n\\t--ck-inspector-color-tree-text-border: #b7b7b7;\\n\\t--ck-inspector-color-tree-node-border-hover: #b0c6e0;\\n\\t--ck-inspector-color-tree-content-delimiter: #ddd;\\n\\t--ck-inspector-color-tree-node-active-bg: #f5faff;\\n\\t--ck-inspector-color-tree-node-name-active-bg: #2B98F0;\\n\\t--ck-inspector-color-tree-node-inactive: #8a8a8a;\\n\\t--ck-inspector-color-tree-selection: #ff1744;\\n\\t--ck-inspector-color-tree-position: black;\\n\\t--ck-inspector-color-comment: green;\\n}\\n\\n.ck-inspector .ck-inspector-tree {\\n\\tbackground: var(--ck-inspector-color-white);\\n\\tpadding: 1em;\\n\\twidth: 100%;\\n\\theight: 100%;\\n\\toverflow: auto;\\n\\tuser-select: none;\\n}\\n\\n/*-- Attribute ---------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-node__attribute {\\n\\tfont: inherit;\\n\\tmargin-left: .4em;\\n\\tcolor: var(--ck-inspector-color-tree-node-tag);\\n\\n\\t& .ck-inspector-tree-node__attribute__name {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-attribute);\\n\\t}\\n\\n\\t& .ck-inspector-tree-node__attribute__value {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-attribute-value);\\n\\n\\t\\t&::before {\\n\\t\\t\\tcontent: '=\\\"'\\n\\t\\t}\\n\\n\\t\\t&::after {\\n\\t\\t\\tcontent: '\\\"'\\n\\t\\t}\\n\\t}\\n}\\n\\n/*-- Node ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-node {\\n\\t& .ck-inspector-tree-node__name {\\n\\t\\tcolor: var(--ck-inspector-color-tree-node-name);\\n\\t\\tdisplay: inline-block;\\n\\t\\twidth: 100%;\\n\\t\\tpadding: 0 .1em;\\n\\t\\tborder-left: 1px solid transparent;\\n\\t}\\n\\n\\t& .ck-inspector-tree-node__name:hover {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-hover);\\n\\t}\\n\\n\\t& .ck-inspector-tree-node__content {\\n\\t\\tpadding-left: 1.5em;\\n\\t\\tpadding-top: 1px;\\n\\t\\tpadding-bottom: 1px;\\n\\t\\tpadding-right: .5em;\\n\\t\\tborder-left: 1px solid var(--ck-inspector-color-tree-content-delimiter);\\n\\t\\t/* Allow multiple white spaces in a series to be displayed */\\n\\t\\twhite-space: pre-wrap;\\n\\t}\\n\\n\\t/*-- Tagged vs. tagless ------------------------------------------------------------------------*/\\n\\n\\t&:not(.ck-inspector-tree-node_tagless) {\\n\\t\\t& .ck-inspector-tree-node__name > .ck-inspector-tree-node__name__bracket_open::after {\\n\\t\\t\\tcontent: \\\"<\\\";\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-tag);\\n\\t\\t}\\n\\n\\t\\t& .ck-inspector-tree-node__name .ck-inspector-tree-node__name__bracket_close::after {\\n\\t\\t\\tcontent: \\\">\\\";\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-tag);\\n\\t\\t}\\n\\n\\t\\t&.ck-inspector-tree-node_empty .ck-inspector-tree-node__name::after {\\n\\t\\t\\tcontent: \\\" />\\\";\\n\\t\\t}\\n\\t}\\n\\n\\t&.ck-inspector-tree-node_tagless .ck-inspector-tree-node__content {\\n\\t\\tdisplay: none;\\n\\t}\\n\\n\\t/*-- Active ------------------------------------------------------------------------*/\\n\\n\\t&.ck-inspector-tree-node_active > .ck-inspector-tree-node__name:not(.ck-inspector-tree-node__name_close) {\\n\\t\\t&,\\n\\t\\t& *:not(.ck-inspector-tree__position),\\n\\t\\t& > .ck-inspector-tree-node__name__bracket::after {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\t}\\n\\n\\t&.ck-inspector-tree-node_active > .ck-inspector-tree-node__content,\\n\\t&.ck-inspector-tree-node_active > .ck-inspector-tree-node__name_close {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-active-bg);\\n\\t}\\n\\n\\t&.ck-inspector-tree-node_active > .ck-inspector-tree-node__content {\\n\\t\\tborder-left-color: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t}\\n\\n\\t&.ck-inspector-tree-node_active > .ck-inspector-tree-node__name {\\n\\t\\tborder-left: 1px solid var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t}\\n\\n\\t/*-- Disabled ------------------------------------------------------------------------*/\\n\\n\\t&.ck-inspector-tree-node_disabled {\\n\\t\\topacity: .8;\\n\\n\\t\\t& .ck-inspector-tree-node__name,\\n\\t\\t& .ck-inspector-tree-node__name * {\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-inactive);\\n\\t\\t}\\n\\t}\\n}\\n\\n/*-- Text ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree-text {\\n\\tdisplay: block;\\n\\tmargin-bottom: 1px;\\n\\n\\t& .ck-inspector-tree-node__content {\\n\\t\\tborder: 1px dotted var(--ck-inspector-color-tree-text-border);\\n\\t\\tborder-radius: 2px;\\n\\t\\tpadding: 0 1px;\\n\\t\\tmargin-right: 1px;\\n\\t\\tdisplay: inline-block;\\n\\t\\tword-break: break-all;\\n\\t}\\n\\n\\t/*-- Attributes ------------------------------------------------------------------------*/\\n\\n\\t& .ck-inspector-tree-text__attributes {\\n\\t\\t&:not(:empty) {\\n\\t\\t\\tmargin-right: .5em;\\n\\t\\t}\\n\\n\\t\\t& .ck-inspector-tree-node__attribute {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-attribute-name);\\n\\t\\t\\tborder-radius: 2px;\\n\\t\\t\\tpadding: 0 .5em;\\n\\n\\t\\t\\t& + .ck-inspector-tree-node__attribute {\\n\\t\\t\\t\\tmargin-left: .2em;\\n\\t\\t\\t}\\n\\n\\t\\t\\t& > * {\\n\\t\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t\\t}\\n\\n\\t\\t\\t&:first-child {\\n\\t\\t\\t\\tmargin-left: 0;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n\\n\\t/*-- Active vs. inactive ------------------------------------------------------------------------*/\\n\\n\\t&.ck-inspector-tree-node_active {\\n\\t\\t& .ck-inspector-tree-node__content {\\n\\t\\t\\tborder-style: solid;\\n\\t\\t\\tborder-color: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t}\\n\\n\\t\\t& .ck-inspector-tree-node__attribute {\\n\\t\\t\\tbackground: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\n\\t\\t& .ck-inspector-tree-node__attribute > * {\\n\\t\\t\\tcolor: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t}\\n\\n\\t\\t& > .ck-inspector-tree-node__content {\\n\\t\\t\\tbackground: var(--ck-inspector-color-tree-node-name-active-bg);\\n\\t\\t\\tcolor: var(--ck-inspector-color-white);\\n\\t\\t}\\n\\t}\\n\\n\\t&:not(.ck-inspector-tree-node_active) .ck-inspector-tree-node__content:hover {\\n\\t\\tbackground: var(--ck-inspector-color-tree-node-hover);\\n\\t\\tborder-style: solid;\\n\\t\\tborder-color: var(--ck-inspector-color-tree-node-border-hover);\\n\\t}\\n}\\n\\n/*-- LTR vs. RTL text ------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_ltr .ck-inspector-tree-node__content {\\n\\tdirection: ltr;\\n}\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_rtl .ck-inspector-tree-node__content {\\n\\tdirection: rtl;\\n\\n\\t& .ck-inspector-tree-node__name {\\n\\t\\tdirection: ltr;\\n\\t}\\n}\\n\\n.ck-inspector-tree.ck-inspector-tree_text-direction_rtl .ck-inspector-tree__position {\\n\\ttransform: rotate(180deg);\\n}\\n\\n\\n/*-- Comment -----------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree .ck-inspector-tree-comment {\\n\\tcolor: var(--ck-inspector-color-comment);\\n\\tfont-style: italic;\\n\\n\\t& a {\\n\\t\\tcolor: inherit;\\n\\t\\ttext-decoration: underline;\\n\\t}\\n}\\n\\n/*-- Compact text ------------------------------------------------------------------------*/\\n\\n.ck-inspector-tree_compact-text .ck-inspector-tree-text {\\n\\tdisplay: inline;\\n}\\n\\n.ck-inspector-tree_compact-text .ck-inspector-tree-text .ck-inspector-tree-node__content {\\n\\tdisplay: inline;\\n}\\n\\n\\n/*-- Navigation ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector .ck-inspector__tree__navigation {\\n\\tpadding: .5em 1em;\\n\\tborder-bottom: 1px solid var(--ck-inspector-color-border);\\n\\n\\t& label {\\n\\t\\tmargin-right: .5em;\\n\\t}\\n}\\n\\n/*-- Selection ---------------------------------------------------------------------------------------- */\\n\\n.ck-inspector-tree .ck-inspector-tree__position {\\n\\tdisplay: inline-block;\\n\\tposition: relative;\\n\\tcursor: default;\\n\\theight: 100%;\\n\\tpointer-events: none;\\n\\n\\t/* When nodes wrap to the new line */\\n\\tvertical-align: top;\\n\\n\\t&::after {\\n\\t\\tcontent: \\\"\\\";\\n\\t\\tposition: absolute;\\n\\t\\tborder: 1px solid var(--ck-inspector-color-tree-position);\\n\\t\\twidth: 0;\\n\\t\\ttop: 0;\\n\\t\\tbottom: 0;\\n\\t\\tmargin-left: -1px;\\n\\t}\\n\\n\\t&::before {\\n\\t\\tmargin-left: -1px;\\n\\t}\\n\\n\\t&.ck-inspector-tree__position_selection {\\n\\t\\tz-index: 2;\\n\\t\\t--ck-inspector-color-tree-position: var(--ck-inspector-color-tree-selection);\\n\\n\\t\\t&::before {\\n\\t\\t\\tcontent: \\\"\\\";\\n\\t\\t\\tposition: absolute;\\n\\t\\t\\ttop: -1px;\\n\\t\\t\\tbottom: -1px;\\n\\t\\t\\tleft: 0;\\n\\t\\t\\tborder-top: 2px solid var(--ck-inspector-color-tree-position);\\n\\t\\t\\tborder-bottom: 2px solid var(--ck-inspector-color-tree-position);\\n\\t\\t\\twidth: 8px;\\n\\t\\t}\\n\\n\\t\\t&.ck-inspector-tree__position_end::before {\\n\\t\\t\\tright: -1px;\\n\\t\\t\\tleft: auto;\\n\\t\\t}\\n\\t}\\n\\n\\t&.ck-inspector-tree__position_marker {\\n\\t\\tz-index: 1;\\n\\n\\t\\t&::before {\\n\\t\\t\\tcontent: \\\"\\\";\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\tposition: absolute;\\n\\t\\t\\tleft: 0;\\n\\t\\t\\ttop: -1px;\\n\\t\\t\\tcursor: default;\\n\\n\\t\\t\\twidth: 0;\\n\\t\\t\\theight: 0;\\n\\t\\t\\tborder-style: solid;\\n\\t\\t\\tborder-width: 7px 7px 0 0;\\n\\t\\t\\tborder-color: var(--ck-inspector-color-tree-position) transparent transparent transparent;\\n\\t\\t}\\n\\n\\t\\t&.ck-inspector-tree__position_end::before {\\n\\t\\t\\tborder-width: 0 7px 7px 0;\\n\\t\\t\\tborder-color: transparent var(--ck-inspector-color-tree-position) transparent transparent;\\n\\t\\t\\tleft: -5px;\\n\\t\\t}\\n\\t}\\n}\\n\"]}]);\n\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/components/tree/tree.css?./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!../ckeditor5-inspector/src/ui.css": +/*!*************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!../ckeditor5-inspector/src/ui.css ***! + \*************************************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("exports = module.exports = __webpack_require__(/*! ../../ckeditor5-mini-inspector/node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\")(true);\n// Module\nexports.push([module.i, \"/**\\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\\n * For licensing, see LICENSE.md.\\n */\\n\\n.ck-inspector {\\n\\t--ck-inspector-color-white: #fff;\\n\\t--ck-inspector-color-black: #000;\\n\\t--ck-inspector-color-background: #F3F3F3;\\n\\t--ck-inspector-color-link: #005CC6;\\n\\t--ck-inspector-code-font-size: 11px;\\n\\t--ck-inspector-code-font-family: monaco,Consolas,Lucida Console,monospace;\\n\\t--ck-inspector-color-border: #D0D0D0;\\n}\\n\\n/* Reset first */\\n.ck-inspector,\\n.ck-inspector *:not(select) {\\n\\tbox-sizing: border-box;\\n\\twidth: auto;\\n\\theight: auto;\\n\\tposition: static;\\n\\n\\tmargin: 0;\\n\\tpadding: 0;\\n\\tborder: 0;\\n\\tbackground: transparent;\\n\\ttext-decoration: none;\\n\\ttransition: none;\\n\\n\\tword-wrap: break-word;\\n\\n\\tfont-family: Arial,Helvetica Neue,Helvetica,sans-serif;\\n\\tfont-size: 12px;\\n\\tline-height: 17px;\\n\\tfont-weight: normal;\\n\\t-webkit-font-smoothing: auto;\\n}\\n\\n.ck-inspector {\\n\\toverflow: hidden;\\n\\tborder-collapse: collapse;\\n\\tcolor: var(--ck-inspector-color-black);\\n\\ttext-align: left;\\n\\twhite-space: normal;\\n\\tcursor: auto;\\n\\tfloat: none;\\n\\n\\tbackground: var(--ck-inspector-color-background);\\n\\tborder-top: 1px solid var(--ck-inspector-color-border);\\n\\tz-index: 9999;\\n}\\n\\n.ck-inspector.ck-inspector_collapsed > .ck-inspector-navbox > .ck-inspector-navbox__navigation .ck-inspector-horizontal-nav {\\n\\tdisplay: none;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__logo {\\n\\tbackground-size: contain;\\n\\tbackground-repeat: no-repeat;\\n\\tbackground-position: center;\\n\\tdisplay: block;\\n\\toverflow: hidden;\\n\\ttext-indent: 100px;\\n\\talign-self: center;\\n\\twhite-space: nowrap;\\n\\tmargin-right: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__logo {\\n\\tbackground-image: url(\\\"data:image/svg+xml;charset=UTF-8,%3csvg width='68' height='64' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='none' fill-rule='evenodd'%3e%3cpath d='M43.71 11.025a11.508 11.508 0 0 0-1.213 5.159c0 6.42 5.244 11.625 11.713 11.625.083 0 .167 0 .25-.002v16.282c0 1.955-1.05 3.761-2.756 4.739L30.986 60.7a5.548 5.548 0 0 1-5.512 0L4.756 48.828A5.464 5.464 0 0 1 2 44.089V20.344c0-1.955 1.05-3.76 2.756-4.738L25.474 3.733a5.548 5.548 0 0 1 5.512 0l12.724 7.292z' fill='%23FFF'/%3e%3cpath d='M45.684 8.79a12.604 12.604 0 0 0-1.329 5.65c0 7.032 5.744 12.733 12.829 12.733.091 0 .183-.001.274-.003v17.834c0 2.14-1.151 4.119-3.019 5.19L31.747 63.196a6.076 6.076 0 0 1-6.037 0L3.02 50.193A5.984 5.984 0 0 1 0 45.003V18.997c0-2.14 1.15-4.119 3.019-5.19L25.71.804a6.076 6.076 0 0 1 6.037 0l13.937 7.986zM16.244 20.68c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h18.479c.833 0 1.509-.67 1.509-1.498v-.715c0-.827-.676-1.498-1.51-1.498H16.244zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49zm41.191-14.459c-5.835 0-10.565-4.695-10.565-10.486 0-5.792 4.73-10.487 10.565-10.487C63.27 3.703 68 8.398 68 14.19c0 5.791-4.73 10.486-10.565 10.486zm3.422-8.68c0-.467-.084-.875-.251-1.225a2.547 2.547 0 0 0-.686-.88 2.888 2.888 0 0 0-1.026-.531 4.418 4.418 0 0 0-1.259-.175c-.134 0-.283.006-.447.018a2.72 2.72 0 0 0-.446.07l.075-1.4h3.587v-1.8h-5.462l-.214 5.06c.319-.116.682-.21 1.089-.28.406-.071.77-.107 1.088-.107.218 0 .437.021.655.063.218.041.413.114.585.218s.313.244.422.419c.109.175.163.391.163.65 0 .424-.132.745-.396.961a1.434 1.434 0 0 1-.938.325c-.352 0-.656-.1-.912-.3-.256-.2-.43-.453-.523-.762l-1.925.588c.1.35.258.664.472.943.214.279.47.514.767.706.298.191.63.339.995.443.365.104.749.156 1.151.156.437 0 .86-.064 1.272-.193.41-.13.778-.323 1.1-.581.324-.258.582-.585.775-.981.193-.396.29-.864.29-1.405z' fill='%231EBC61' fill-rule='nonzero'/%3e%3c/g%3e%3c/svg%3e\\\");\\n\\twidth: 1.8em;\\n\\theight: 1.8em;\\n\\tmargin-left: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__toggle {\\n\\tmargin-right: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__toggle.ck-inspector-navbox__navigation__toggle_up {\\n\\ttransform: rotate(180deg);\\n}\\n\\n.ck-inspector .ck-inspector-editor-selector {\\n\\tmargin-left: auto;\\n\\tmargin-right: .3em\\n}\\n\\n.ck-inspector .ck-inspector-editor-selector select {\\n\\t\\tmargin-left: .5em;\\n\\t}\\n\\n.ck-inspector .ck-inspector-code,\\n\\t.ck-inspector .ck-inspector-code * {\\n\\t\\tfont-size: var(--ck-inspector-code-font-size);\\n\\t\\tfont-family: var(--ck-inspector-code-font-family);\\n\\t\\tcursor: default;\\n\\t}\\n\\n.ck-inspector a {\\n\\tcolor: var(--ck-inspector-color-link);\\n\\ttext-decoration: none;\\n}\\n\\n.ck-inspector a:hover {\\n\\ttext-decoration: underline;\\n\\tcursor: pointer;\\n}\\n\\n.ck-inspector button {\\n\\toutline: 0;\\n}\\n\\n.ck-inspector .ck-inspector-separator {\\n\\tborder-right: 1px solid var(--ck-inspector-color-border);\\n\\tdisplay: inline-block;\\n\\twidth: 0px;\\n\\theight: 20px;\\n\\tmargin: 0 .5em;\\n\\tvertical-align: middle;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"ui.css\"],\"names\":[],\"mappings\":\"AAAA;;;EAGE;;AAEF;CACC,gCAAgC;CAChC,gCAAgC;CAChC,wCAAwC;CACxC,kCAAkC;CAClC,mCAAmC;CACnC,yEAAyE;CACzE,oCAAoC;AACrC;;AAEA,gBAAgB;AAChB;;CAEC,sBAAsB;CACtB,WAAW;CACX,YAAY;CACZ,gBAAgB;;CAEhB,SAAS;CACT,UAAU;CACV,SAAS;CACT,uBAAuB;CACvB,qBAAqB;CACrB,gBAAgB;;CAEhB,qBAAqB;;CAErB,sDAAsD;CACtD,eAAe;CACf,iBAAiB;CACjB,mBAAmB;CACnB,4BAA4B;AAC7B;;AAEA;CACC,gBAAgB;CAChB,yBAAyB;CACzB,sCAAsC;CACtC,gBAAgB;CAChB,mBAAmB;CACnB,YAAY;CACZ,WAAW;;CAEX,gDAAgD;CAChD,sDAAsD;CACtD,aAAa;AACd;;AAEA;CACC,aAAa;AACd;;AAEA;CACC,wBAAwB;CACxB,4BAA4B;CAC5B,2BAA2B;CAC3B,cAAc;CACd,gBAAgB;CAChB,kBAAkB;CAClB,kBAAkB;CAClB,mBAAmB;CACnB,iBAAiB;AAClB;;AAEA;CACC,4lEAA4lE;CAC5lE,YAAY;CACZ,aAAa;CACb,gBAAgB;AACjB;;AAEA;CACC,iBAAiB;AAClB;;AAEA;CACC,yBAAyB;AAC1B;;AAEA;CACC,iBAAiB;CACjB;AAKD;;AAHC;EACC,iBAAiB;CAClB;;AAIA;;EAEC,6CAA6C;EAC7C,iDAAiD;EACjD,eAAe;CAChB;;AAGD;CACC,qCAAqC;CACrC,qBAAqB;AACtB;;AAEA;CACC,0BAA0B;CAC1B,eAAe;AAChB;;AAEA;CACC,UAAU;AACX;;AAEA;CACC,wDAAwD;CACxD,qBAAqB;CACrB,UAAU;CACV,YAAY;CACZ,cAAc;CACd,sBAAsB;AACvB\",\"file\":\"ui.css\",\"sourcesContent\":[\"/**\\n * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.\\n * For licensing, see LICENSE.md.\\n */\\n\\n.ck-inspector {\\n\\t--ck-inspector-color-white: #fff;\\n\\t--ck-inspector-color-black: #000;\\n\\t--ck-inspector-color-background: #F3F3F3;\\n\\t--ck-inspector-color-link: #005CC6;\\n\\t--ck-inspector-code-font-size: 11px;\\n\\t--ck-inspector-code-font-family: monaco,Consolas,Lucida Console,monospace;\\n\\t--ck-inspector-color-border: #D0D0D0;\\n}\\n\\n/* Reset first */\\n.ck-inspector,\\n.ck-inspector *:not(select) {\\n\\tbox-sizing: border-box;\\n\\twidth: auto;\\n\\theight: auto;\\n\\tposition: static;\\n\\n\\tmargin: 0;\\n\\tpadding: 0;\\n\\tborder: 0;\\n\\tbackground: transparent;\\n\\ttext-decoration: none;\\n\\ttransition: none;\\n\\n\\tword-wrap: break-word;\\n\\n\\tfont-family: Arial,Helvetica Neue,Helvetica,sans-serif;\\n\\tfont-size: 12px;\\n\\tline-height: 17px;\\n\\tfont-weight: normal;\\n\\t-webkit-font-smoothing: auto;\\n}\\n\\n.ck-inspector {\\n\\toverflow: hidden;\\n\\tborder-collapse: collapse;\\n\\tcolor: var(--ck-inspector-color-black);\\n\\ttext-align: left;\\n\\twhite-space: normal;\\n\\tcursor: auto;\\n\\tfloat: none;\\n\\n\\tbackground: var(--ck-inspector-color-background);\\n\\tborder-top: 1px solid var(--ck-inspector-color-border);\\n\\tz-index: 9999;\\n}\\n\\n.ck-inspector.ck-inspector_collapsed > .ck-inspector-navbox > .ck-inspector-navbox__navigation .ck-inspector-horizontal-nav {\\n\\tdisplay: none;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__logo {\\n\\tbackground-size: contain;\\n\\tbackground-repeat: no-repeat;\\n\\tbackground-position: center;\\n\\tdisplay: block;\\n\\toverflow: hidden;\\n\\ttext-indent: 100px;\\n\\talign-self: center;\\n\\twhite-space: nowrap;\\n\\tmargin-right: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__logo {\\n\\tbackground-image: url(\\\"data:image/svg+xml;charset=UTF-8,%3csvg width='68' height='64' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='none' fill-rule='evenodd'%3e%3cpath d='M43.71 11.025a11.508 11.508 0 0 0-1.213 5.159c0 6.42 5.244 11.625 11.713 11.625.083 0 .167 0 .25-.002v16.282c0 1.955-1.05 3.761-2.756 4.739L30.986 60.7a5.548 5.548 0 0 1-5.512 0L4.756 48.828A5.464 5.464 0 0 1 2 44.089V20.344c0-1.955 1.05-3.76 2.756-4.738L25.474 3.733a5.548 5.548 0 0 1 5.512 0l12.724 7.292z' fill='%23FFF'/%3e%3cpath d='M45.684 8.79a12.604 12.604 0 0 0-1.329 5.65c0 7.032 5.744 12.733 12.829 12.733.091 0 .183-.001.274-.003v17.834c0 2.14-1.151 4.119-3.019 5.19L31.747 63.196a6.076 6.076 0 0 1-6.037 0L3.02 50.193A5.984 5.984 0 0 1 0 45.003V18.997c0-2.14 1.15-4.119 3.019-5.19L25.71.804a6.076 6.076 0 0 1 6.037 0l13.937 7.986zM16.244 20.68c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h18.479c.833 0 1.509-.67 1.509-1.498v-.715c0-.827-.676-1.498-1.51-1.498H16.244zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49zm41.191-14.459c-5.835 0-10.565-4.695-10.565-10.486 0-5.792 4.73-10.487 10.565-10.487C63.27 3.703 68 8.398 68 14.19c0 5.791-4.73 10.486-10.565 10.486zm3.422-8.68c0-.467-.084-.875-.251-1.225a2.547 2.547 0 0 0-.686-.88 2.888 2.888 0 0 0-1.026-.531 4.418 4.418 0 0 0-1.259-.175c-.134 0-.283.006-.447.018a2.72 2.72 0 0 0-.446.07l.075-1.4h3.587v-1.8h-5.462l-.214 5.06c.319-.116.682-.21 1.089-.28.406-.071.77-.107 1.088-.107.218 0 .437.021.655.063.218.041.413.114.585.218s.313.244.422.419c.109.175.163.391.163.65 0 .424-.132.745-.396.961a1.434 1.434 0 0 1-.938.325c-.352 0-.656-.1-.912-.3-.256-.2-.43-.453-.523-.762l-1.925.588c.1.35.258.664.472.943.214.279.47.514.767.706.298.191.63.339.995.443.365.104.749.156 1.151.156.437 0 .86-.064 1.272-.193.41-.13.778-.323 1.1-.581.324-.258.582-.585.775-.981.193-.396.29-.864.29-1.405z' fill='%231EBC61' fill-rule='nonzero'/%3e%3c/g%3e%3c/svg%3e\\\");\\n\\twidth: 1.8em;\\n\\theight: 1.8em;\\n\\tmargin-left: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__toggle {\\n\\tmargin-right: 1em;\\n}\\n\\n.ck-inspector .ck-inspector-navbox__navigation__toggle.ck-inspector-navbox__navigation__toggle_up {\\n\\ttransform: rotate(180deg);\\n}\\n\\n.ck-inspector .ck-inspector-editor-selector {\\n\\tmargin-left: auto;\\n\\tmargin-right: .3em;\\n\\n\\t& select {\\n\\t\\tmargin-left: .5em;\\n\\t}\\n}\\n\\n.ck-inspector .ck-inspector-code {\\n\\t&,\\n\\t& * {\\n\\t\\tfont-size: var(--ck-inspector-code-font-size);\\n\\t\\tfont-family: var(--ck-inspector-code-font-family);\\n\\t\\tcursor: default;\\n\\t}\\n}\\n\\n.ck-inspector a {\\n\\tcolor: var(--ck-inspector-color-link);\\n\\ttext-decoration: none;\\n}\\n\\n.ck-inspector a:hover {\\n\\ttext-decoration: underline;\\n\\tcursor: pointer;\\n}\\n\\n.ck-inspector button {\\n\\toutline: 0;\\n}\\n\\n.ck-inspector .ck-inspector-separator {\\n\\tborder-right: 1px solid var(--ck-inspector-color-border);\\n\\tdisplay: inline-block;\\n\\twidth: 0px;\\n\\theight: 20px;\\n\\tmargin: 0 .5em;\\n\\tvertical-align: middle;\\n}\\n\"]}]);\n\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/../ckeditor5-inspector/src/ui.css?./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!./src/ui.css": +/*!****************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./src/ui.css ***! + \****************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("exports = module.exports = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\")(true);\n// Module\nexports.push([module.i, \".ck-inspector.ck-mini-inspector {\\n\\tdisplay: flex;\\n\\tflex-direction: row;\\n\\tflex-wrap: nowrap;\\n\\talign-items: stretch;\\n\\tborder-top: 0\\n}\\n.ck-inspector.ck-mini-inspector > * {\\n\\t\\theight: auto;\\n\\t\\tbackground: white\\n\\t}\\n.ck-inspector.ck-mini-inspector > *:first-child::before {\\n\\t\\t\\tcontent: \\\"Model\\\";\\n\\t\\t}\\n.ck-inspector.ck-mini-inspector > *:last-child::before {\\n\\t\\t\\tcontent: \\\"View\\\";\\n\\t\\t}\\n.ck-inspector.ck-mini-inspector > *:first-child::before,\\n\\t\\t.ck-inspector.ck-mini-inspector > *:last-child::before {\\n\\t\\t\\tposition: relative;\\n\\t\\t\\tleft: -10px;\\n\\t\\t\\twidth: calc(100% + 20px);\\n\\t\\t\\tbackground: #eee;\\n\\t\\t\\tfont-size: 18px;\\n\\t\\t\\tfont-weight: bold;\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\tpadding: 6px 10px;\\n\\t\\t\\tmargin-bottom: 20px;\\n\\t\\t}\\n.ck-inspector.ck-mini-inspector .ck-inspector-tree {\\n\\t\\tpadding: 0 10px 20px;\\n\\t\\tborder: 1px solid #ccc;\\n\\t}\\n\", \"\",{\"version\":3,\"sources\":[\"ui.css\"],\"names\":[],\"mappings\":\"AAAA;CACC,aAAa;CACb,mBAAmB;CACnB,iBAAiB;CACjB,oBAAoB;CACpB;AAgCD;AA9BC;EACC,YAAY;EACZ;CAsBD;AApBC;GACC,gBAAgB;EACjB;AAEA;GACC,eAAe;EAChB;AAEA;;GAEC,kBAAkB;GAClB,WAAW;GACX,wBAAwB;GACxB,gBAAgB;GAChB,eAAe;GACf,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,mBAAmB;EACpB;AAGD;EACC,oBAAoB;EACpB,sBAAsB;CACvB\",\"file\":\"ui.css\",\"sourcesContent\":[\".ck-inspector.ck-mini-inspector {\\n\\tdisplay: flex;\\n\\tflex-direction: row;\\n\\tflex-wrap: nowrap;\\n\\talign-items: stretch;\\n\\tborder-top: 0;\\n\\n\\t& > * {\\n\\t\\theight: auto;\\n\\t\\tbackground: white;\\n\\n\\t\\t&:first-child::before {\\n\\t\\t\\tcontent: \\\"Model\\\";\\n\\t\\t}\\n\\n\\t\\t&:last-child::before {\\n\\t\\t\\tcontent: \\\"View\\\";\\n\\t\\t}\\n\\n\\t\\t&:first-child::before,\\n\\t\\t&:last-child::before {\\n\\t\\t\\tposition: relative;\\n\\t\\t\\tleft: -10px;\\n\\t\\t\\twidth: calc(100% + 20px);\\n\\t\\t\\tbackground: #eee;\\n\\t\\t\\tfont-size: 18px;\\n\\t\\t\\tfont-weight: bold;\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\tpadding: 6px 10px;\\n\\t\\t\\tmargin-bottom: 20px;\\n\\t\\t}\\n\\t}\\n\\n\\t& .ck-inspector-tree {\\n\\t\\tpadding: 0 10px 20px;\\n\\t\\tborder: 1px solid #ccc;\\n\\t}\\n}\\n\"]}]);\n\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./src/ui.css?./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/api.js": +/*!*****************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/api.js ***! + \*****************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\nmodule.exports = function (useSourceMap) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = cssWithMappingToString(item, useSourceMap);\n\n if (item[2]) {\n return '@media ' + item[2] + '{' + content + '}';\n } else {\n return content;\n }\n }).join('');\n }; // import a list of modules into the list\n\n\n list.i = function (modules, mediaQuery) {\n if (typeof modules === 'string') {\n modules = [[null, modules, '']];\n }\n\n var alreadyImportedModules = {};\n\n for (var i = 0; i < this.length; i++) {\n var id = this[i][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n\n for (i = 0; i < modules.length; i++) {\n var item = modules[i]; // skip already imported module\n // this implementation is not 100% perfect for weird media query combinations\n // when a module is imported multiple times with different media queries.\n // I hope this will never occur (Hey this way we have smaller bundles)\n\n if (item[0] == null || !alreadyImportedModules[item[0]]) {\n if (mediaQuery && !item[2]) {\n item[2] = mediaQuery;\n } else if (mediaQuery) {\n item[2] = '(' + item[2] + ') and (' + mediaQuery + ')';\n }\n\n list.push(item);\n }\n }\n };\n\n return list;\n};\n\nfunction cssWithMappingToString(item, useSourceMap) {\n var content = item[1] || '';\n var cssMapping = item[3];\n\n if (!cssMapping) {\n return content;\n }\n\n if (useSourceMap && typeof btoa === 'function') {\n var sourceMapping = toComment(cssMapping);\n var sourceURLs = cssMapping.sources.map(function (source) {\n return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */';\n });\n return [content].concat(sourceURLs).concat([sourceMapping]).join('\\n');\n }\n\n return [content].join('\\n');\n} // Adapted from convert-source-map (MIT)\n\n\nfunction toComment(sourceMap) {\n // eslint-disable-next-line no-undef\n var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));\n var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;\n return '/*# ' + data + ' */';\n}\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/css-loader/dist/runtime/api.js?"); + +/***/ }), + +/***/ "./node_modules/ieee754/index.js": +/*!***************************************!*\ + !*** ./node_modules/ieee754/index.js ***! + \***************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +eval("/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */\nexports.read = function (buffer, offset, isLE, mLen, nBytes) {\n var e, m\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var nBits = -7\n var i = isLE ? (nBytes - 1) : 0\n var d = isLE ? -1 : 1\n var s = buffer[offset + i]\n\n i += d\n\n e = s & ((1 << (-nBits)) - 1)\n s >>= (-nBits)\n nBits += eLen\n for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n m = e & ((1 << (-nBits)) - 1)\n e >>= (-nBits)\n nBits += mLen\n for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n if (e === 0) {\n e = 1 - eBias\n } else if (e === eMax) {\n return m ? NaN : ((s ? -1 : 1) * Infinity)\n } else {\n m = m + Math.pow(2, mLen)\n e = e - eBias\n }\n return (s ? -1 : 1) * m * Math.pow(2, e - mLen)\n}\n\nexports.write = function (buffer, value, offset, isLE, mLen, nBytes) {\n var e, m, c\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)\n var i = isLE ? 0 : (nBytes - 1)\n var d = isLE ? 1 : -1\n var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0\n\n value = Math.abs(value)\n\n if (isNaN(value) || value === Infinity) {\n m = isNaN(value) ? 1 : 0\n e = eMax\n } else {\n e = Math.floor(Math.log(value) / Math.LN2)\n if (value * (c = Math.pow(2, -e)) < 1) {\n e--\n c *= 2\n }\n if (e + eBias >= 1) {\n value += rt / c\n } else {\n value += rt * Math.pow(2, 1 - eBias)\n }\n if (value * c >= 2) {\n e++\n c /= 2\n }\n\n if (e + eBias >= eMax) {\n m = 0\n e = eMax\n } else if (e + eBias >= 1) {\n m = ((value * c) - 1) * Math.pow(2, mLen)\n e = e + eBias\n } else {\n m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)\n e = 0\n }\n }\n\n for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}\n\n e = (e << mLen) | m\n eLen += mLen\n for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}\n\n buffer[offset + i - d] |= s * 128\n}\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/ieee754/index.js?"); + +/***/ }), + +/***/ "./node_modules/isarray/index.js": +/*!***************************************!*\ + !*** ./node_modules/isarray/index.js ***! + \***************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +eval("var toString = {}.toString;\n\nmodule.exports = Array.isArray || function (arr) {\n return toString.call(arr) == '[object Array]';\n};\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/isarray/index.js?"); + +/***/ }), + +/***/ "./node_modules/object-assign/index.js": +/*!*********************************************!*\ + !*** ./node_modules/object-assign/index.js ***! + \*********************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/object-assign/index.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/checkPropTypes.js": +/*!***************************************************!*\ + !*** ./node_modules/prop-types/checkPropTypes.js ***! + \***************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar printWarning = function() {};\n\nif (true) {\n var ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"./node_modules/prop-types/lib/ReactPropTypesSecret.js\");\n var loggedTypeFailures = {};\n var has = Function.call.bind(Object.prototype.hasOwnProperty);\n\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\n/**\n * Assert that the values match with the type specs.\n * Error messages are memorized and will only be shown once.\n *\n * @param {object} typeSpecs Map of name to a ReactPropType\n * @param {object} values Runtime values that need to be type-checked\n * @param {string} location e.g. \"prop\", \"context\", \"child context\"\n * @param {string} componentName Name of the component for error messages.\n * @param {?Function} getStack Returns the component stack.\n * @private\n */\nfunction checkPropTypes(typeSpecs, values, location, componentName, getStack) {\n if (true) {\n for (var typeSpecName in typeSpecs) {\n if (has(typeSpecs, typeSpecName)) {\n var error;\n // Prop type validation may throw. In case they do, we don't want to\n // fail the render phase where it didn't fail before. So we log it.\n // After these have been cleaned up, we'll let them throw.\n try {\n // This is intentionally an invariant that gets caught. It's the same\n // behavior as without this statement except with a better message.\n if (typeof typeSpecs[typeSpecName] !== 'function') {\n var err = Error(\n (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +\n 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.'\n );\n err.name = 'Invariant Violation';\n throw err;\n }\n error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);\n } catch (ex) {\n error = ex;\n }\n if (error && !(error instanceof Error)) {\n printWarning(\n (componentName || 'React class') + ': type specification of ' +\n location + ' `' + typeSpecName + '` is invalid; the type checker ' +\n 'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +\n 'You may have forgotten to pass an argument to the type checker ' +\n 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +\n 'shape all require an argument).'\n );\n }\n if (error instanceof Error && !(error.message in loggedTypeFailures)) {\n // Only monitor this failure once because there tends to be a lot of the\n // same error.\n loggedTypeFailures[error.message] = true;\n\n var stack = getStack ? getStack() : '';\n\n printWarning(\n 'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')\n );\n }\n }\n }\n }\n}\n\n/**\n * Resets warning cache when testing.\n *\n * @private\n */\ncheckPropTypes.resetWarningCache = function() {\n if (true) {\n loggedTypeFailures = {};\n }\n}\n\nmodule.exports = checkPropTypes;\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/prop-types/checkPropTypes.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/lib/ReactPropTypesSecret.js": +/*!*************************************************************!*\ + !*** ./node_modules/prop-types/lib/ReactPropTypesSecret.js ***! + \*************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\n\n\n//# sourceURL=webpack://MiniCKEditorInspector/./node_modules/prop-types/lib/ReactPropTypesSecret.js?"); + +/***/ }), + +/***/ "./node_modules/react-dom/cjs/react-dom.development.js": +/*!*************************************************************!*\ + !*** ./node_modules/react-dom/cjs/react-dom.development.js ***! + \*************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("/** @license React v16.14.0\n * react-dom.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\n\n\nif (true) {\n (function() {\n'use strict';\n\nvar React = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\nvar _assign = __webpack_require__(/*! object-assign */ \"./node_modules/object-assign/index.js\");\nvar Scheduler = __webpack_require__(/*! scheduler */ \"./node_modules/scheduler/index.js\");\nvar checkPropTypes = __webpack_require__(/*! prop-types/checkPropTypes */ \"./node_modules/prop-types/checkPropTypes.js\");\nvar tracing = __webpack_require__(/*! scheduler/tracing */ \"./node_modules/scheduler/tracing.js\");\n\nvar ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; // Prevent newer renderers from RTE when used with older react package versions.\n// Current owner and dispatcher used to share the same ref,\n// but PR #14548 split them out to better support the react-debug-tools package.\n\nif (!ReactSharedInternals.hasOwnProperty('ReactCurrentDispatcher')) {\n ReactSharedInternals.ReactCurrentDispatcher = {\n current: null\n };\n}\n\nif (!ReactSharedInternals.hasOwnProperty('ReactCurrentBatchConfig')) {\n ReactSharedInternals.ReactCurrentBatchConfig = {\n suspense: null\n };\n}\n\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n printWarning('warn', format, args);\n }\n}\nfunction error(format) {\n {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var hasExistingStack = args.length > 0 && typeof args[args.length - 1] === 'string' && args[args.length - 1].indexOf('\\n in') === 0;\n\n if (!hasExistingStack) {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n }\n }\n\n var argsWithFormat = args.map(function (item) {\n return '' + item;\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n var argIndex = 0;\n var message = 'Warning: ' + format.replace(/%s/g, function () {\n return args[argIndex++];\n });\n throw new Error(message);\n } catch (x) {}\n }\n}\n\nif (!React) {\n {\n throw Error( \"ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.\" );\n }\n}\n\nvar invokeGuardedCallbackImpl = function (name, func, context, a, b, c, d, e, f) {\n var funcArgs = Array.prototype.slice.call(arguments, 3);\n\n try {\n func.apply(context, funcArgs);\n } catch (error) {\n this.onError(error);\n }\n};\n\n{\n // In DEV mode, we swap out invokeGuardedCallback for a special version\n // that plays more nicely with the browser's DevTools. The idea is to preserve\n // \"Pause on exceptions\" behavior. Because React wraps all user-provided\n // functions in invokeGuardedCallback, and the production version of\n // invokeGuardedCallback uses a try-catch, all user exceptions are treated\n // like caught exceptions, and the DevTools won't pause unless the developer\n // takes the extra step of enabling pause on caught exceptions. This is\n // unintuitive, though, because even though React has caught the error, from\n // the developer's perspective, the error is uncaught.\n //\n // To preserve the expected \"Pause on exceptions\" behavior, we don't use a\n // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake\n // DOM node, and call the user-provided callback from inside an event handler\n // for that fake event. If the callback throws, the error is \"captured\" using\n // a global event handler. But because the error happens in a different\n // event loop context, it does not interrupt the normal program flow.\n // Effectively, this gives us try-catch behavior without actually using\n // try-catch. Neat!\n // Check that the browser supports the APIs we need to implement our special\n // DEV version of invokeGuardedCallback\n if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') {\n var fakeNode = document.createElement('react');\n\n var invokeGuardedCallbackDev = function (name, func, context, a, b, c, d, e, f) {\n // If document doesn't exist we know for sure we will crash in this method\n // when we call document.createEvent(). However this can cause confusing\n // errors: https://github.com/facebookincubator/create-react-app/issues/3482\n // So we preemptively throw with a better message instead.\n if (!(typeof document !== 'undefined')) {\n {\n throw Error( \"The `document` global was defined when React was initialized, but is not defined anymore. This can happen in a test environment if a component schedules an update from an asynchronous callback, but the test has already finished running. To solve this, you can either unmount the component at the end of your test (and ensure that any asynchronous operations get canceled in `componentWillUnmount`), or you can change the test itself to be asynchronous.\" );\n }\n }\n\n var evt = document.createEvent('Event'); // Keeps track of whether the user-provided callback threw an error. We\n // set this to true at the beginning, then set it to false right after\n // calling the function. If the function errors, `didError` will never be\n // set to false. This strategy works even if the browser is flaky and\n // fails to call our global error handler, because it doesn't rely on\n // the error event at all.\n\n var didError = true; // Keeps track of the value of window.event so that we can reset it\n // during the callback to let user code access window.event in the\n // browsers that support it.\n\n var windowEvent = window.event; // Keeps track of the descriptor of window.event to restore it after event\n // dispatching: https://github.com/facebook/react/issues/13688\n\n var windowEventDescriptor = Object.getOwnPropertyDescriptor(window, 'event'); // Create an event handler for our fake event. We will synchronously\n // dispatch our fake event using `dispatchEvent`. Inside the handler, we\n // call the user-provided callback.\n\n var funcArgs = Array.prototype.slice.call(arguments, 3);\n\n function callCallback() {\n // We immediately remove the callback from event listeners so that\n // nested `invokeGuardedCallback` calls do not clash. Otherwise, a\n // nested call would trigger the fake event handlers of any call higher\n // in the stack.\n fakeNode.removeEventListener(evtType, callCallback, false); // We check for window.hasOwnProperty('event') to prevent the\n // window.event assignment in both IE <= 10 as they throw an error\n // \"Member not found\" in strict mode, and in Firefox which does not\n // support window.event.\n\n if (typeof window.event !== 'undefined' && window.hasOwnProperty('event')) {\n window.event = windowEvent;\n }\n\n func.apply(context, funcArgs);\n didError = false;\n } // Create a global error event handler. We use this to capture the value\n // that was thrown. It's possible that this error handler will fire more\n // than once; for example, if non-React code also calls `dispatchEvent`\n // and a handler for that event throws. We should be resilient to most of\n // those cases. Even if our error event handler fires more than once, the\n // last error event is always used. If the callback actually does error,\n // we know that the last error event is the correct one, because it's not\n // possible for anything else to have happened in between our callback\n // erroring and the code that follows the `dispatchEvent` call below. If\n // the callback doesn't error, but the error event was fired, we know to\n // ignore it because `didError` will be false, as described above.\n\n\n var error; // Use this to track whether the error event is ever called.\n\n var didSetError = false;\n var isCrossOriginError = false;\n\n function handleWindowError(event) {\n error = event.error;\n didSetError = true;\n\n if (error === null && event.colno === 0 && event.lineno === 0) {\n isCrossOriginError = true;\n }\n\n if (event.defaultPrevented) {\n // Some other error handler has prevented default.\n // Browsers silence the error report if this happens.\n // We'll remember this to later decide whether to log it or not.\n if (error != null && typeof error === 'object') {\n try {\n error._suppressLogging = true;\n } catch (inner) {// Ignore.\n }\n }\n }\n } // Create a fake event type.\n\n\n var evtType = \"react-\" + (name ? name : 'invokeguardedcallback'); // Attach our event handlers\n\n window.addEventListener('error', handleWindowError);\n fakeNode.addEventListener(evtType, callCallback, false); // Synchronously dispatch our fake event. If the user-provided function\n // errors, it will trigger our global error handler.\n\n evt.initEvent(evtType, false, false);\n fakeNode.dispatchEvent(evt);\n\n if (windowEventDescriptor) {\n Object.defineProperty(window, 'event', windowEventDescriptor);\n }\n\n if (didError) {\n if (!didSetError) {\n // The callback errored, but the error event never fired.\n error = new Error('An error was thrown inside one of your components, but React ' + \"doesn't know what it was. This is likely due to browser \" + 'flakiness. React does its best to preserve the \"Pause on ' + 'exceptions\" behavior of the DevTools, which requires some ' + \"DEV-mode only tricks. It's possible that these don't work in \" + 'your browser. Try triggering the error in production mode, ' + 'or switching to a modern browser. If you suspect that this is ' + 'actually an issue with React, please file an issue.');\n } else if (isCrossOriginError) {\n error = new Error(\"A cross-origin error was thrown. React doesn't have access to \" + 'the actual error object in development. ' + 'See https://fb.me/react-crossorigin-error for more information.');\n }\n\n this.onError(error);\n } // Remove our event listeners\n\n\n window.removeEventListener('error', handleWindowError);\n };\n\n invokeGuardedCallbackImpl = invokeGuardedCallbackDev;\n }\n}\n\nvar invokeGuardedCallbackImpl$1 = invokeGuardedCallbackImpl;\n\nvar hasError = false;\nvar caughtError = null; // Used by event system to capture/rethrow the first error.\n\nvar hasRethrowError = false;\nvar rethrowError = null;\nvar reporter = {\n onError: function (error) {\n hasError = true;\n caughtError = error;\n }\n};\n/**\n * Call a function while guarding against errors that happens within it.\n * Returns an error if it throws, otherwise null.\n *\n * In production, this is implemented using a try-catch. The reason we don't\n * use a try-catch directly is so that we can swap out a different\n * implementation in DEV mode.\n *\n * @param {String} name of the guard to use for logging or debugging\n * @param {Function} func The function to invoke\n * @param {*} context The context to use when calling the function\n * @param {...*} args Arguments for function\n */\n\nfunction invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {\n hasError = false;\n caughtError = null;\n invokeGuardedCallbackImpl$1.apply(reporter, arguments);\n}\n/**\n * Same as invokeGuardedCallback, but instead of returning an error, it stores\n * it in a global so it can be rethrown by `rethrowCaughtError` later.\n * TODO: See if caughtError and rethrowError can be unified.\n *\n * @param {String} name of the guard to use for logging or debugging\n * @param {Function} func The function to invoke\n * @param {*} context The context to use when calling the function\n * @param {...*} args Arguments for function\n */\n\nfunction invokeGuardedCallbackAndCatchFirstError(name, func, context, a, b, c, d, e, f) {\n invokeGuardedCallback.apply(this, arguments);\n\n if (hasError) {\n var error = clearCaughtError();\n\n if (!hasRethrowError) {\n hasRethrowError = true;\n rethrowError = error;\n }\n }\n}\n/**\n * During execution of guarded functions we will capture the first error which\n * we will rethrow to be handled by the top level error handler.\n */\n\nfunction rethrowCaughtError() {\n if (hasRethrowError) {\n var error = rethrowError;\n hasRethrowError = false;\n rethrowError = null;\n throw error;\n }\n}\nfunction hasCaughtError() {\n return hasError;\n}\nfunction clearCaughtError() {\n if (hasError) {\n var error = caughtError;\n hasError = false;\n caughtError = null;\n return error;\n } else {\n {\n {\n throw Error( \"clearCaughtError was called but no error was captured. This error is likely caused by a bug in React. Please file an issue.\" );\n }\n }\n }\n}\n\nvar getFiberCurrentPropsFromNode = null;\nvar getInstanceFromNode = null;\nvar getNodeFromInstance = null;\nfunction setComponentTree(getFiberCurrentPropsFromNodeImpl, getInstanceFromNodeImpl, getNodeFromInstanceImpl) {\n getFiberCurrentPropsFromNode = getFiberCurrentPropsFromNodeImpl;\n getInstanceFromNode = getInstanceFromNodeImpl;\n getNodeFromInstance = getNodeFromInstanceImpl;\n\n {\n if (!getNodeFromInstance || !getInstanceFromNode) {\n error('EventPluginUtils.setComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.');\n }\n }\n}\nvar validateEventDispatches;\n\n{\n validateEventDispatches = function (event) {\n var dispatchListeners = event._dispatchListeners;\n var dispatchInstances = event._dispatchInstances;\n var listenersIsArr = Array.isArray(dispatchListeners);\n var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0;\n var instancesIsArr = Array.isArray(dispatchInstances);\n var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0;\n\n if (instancesIsArr !== listenersIsArr || instancesLen !== listenersLen) {\n error('EventPluginUtils: Invalid `event`.');\n }\n };\n}\n/**\n * Dispatch the event to the listener.\n * @param {SyntheticEvent} event SyntheticEvent to handle\n * @param {function} listener Application-level callback\n * @param {*} inst Internal component instance\n */\n\n\nfunction executeDispatch(event, listener, inst) {\n var type = event.type || 'unknown-event';\n event.currentTarget = getNodeFromInstance(inst);\n invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);\n event.currentTarget = null;\n}\n/**\n * Standard/simple iteration through an event's collected dispatches.\n */\n\nfunction executeDispatchesInOrder(event) {\n var dispatchListeners = event._dispatchListeners;\n var dispatchInstances = event._dispatchInstances;\n\n {\n validateEventDispatches(event);\n }\n\n if (Array.isArray(dispatchListeners)) {\n for (var i = 0; i < dispatchListeners.length; i++) {\n if (event.isPropagationStopped()) {\n break;\n } // Listeners and Instances are two parallel arrays that are always in sync.\n\n\n executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);\n }\n } else if (dispatchListeners) {\n executeDispatch(event, dispatchListeners, dispatchInstances);\n }\n\n event._dispatchListeners = null;\n event._dispatchInstances = null;\n}\n\nvar FunctionComponent = 0;\nvar ClassComponent = 1;\nvar IndeterminateComponent = 2; // Before we know whether it is function or class\n\nvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.\n\nvar HostPortal = 4; // A subtree. Could be an entry point to a different renderer.\n\nvar HostComponent = 5;\nvar HostText = 6;\nvar Fragment = 7;\nvar Mode = 8;\nvar ContextConsumer = 9;\nvar ContextProvider = 10;\nvar ForwardRef = 11;\nvar Profiler = 12;\nvar SuspenseComponent = 13;\nvar MemoComponent = 14;\nvar SimpleMemoComponent = 15;\nvar LazyComponent = 16;\nvar IncompleteClassComponent = 17;\nvar DehydratedFragment = 18;\nvar SuspenseListComponent = 19;\nvar FundamentalComponent = 20;\nvar ScopeComponent = 21;\nvar Block = 22;\n\n/**\n * Injectable ordering of event plugins.\n */\nvar eventPluginOrder = null;\n/**\n * Injectable mapping from names to event plugin modules.\n */\n\nvar namesToPlugins = {};\n/**\n * Recomputes the plugin list using the injected plugins and plugin ordering.\n *\n * @private\n */\n\nfunction recomputePluginOrdering() {\n if (!eventPluginOrder) {\n // Wait until an `eventPluginOrder` is injected.\n return;\n }\n\n for (var pluginName in namesToPlugins) {\n var pluginModule = namesToPlugins[pluginName];\n var pluginIndex = eventPluginOrder.indexOf(pluginName);\n\n if (!(pluginIndex > -1)) {\n {\n throw Error( \"EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `\" + pluginName + \"`.\" );\n }\n }\n\n if (plugins[pluginIndex]) {\n continue;\n }\n\n if (!pluginModule.extractEvents) {\n {\n throw Error( \"EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `\" + pluginName + \"` does not.\" );\n }\n }\n\n plugins[pluginIndex] = pluginModule;\n var publishedEvents = pluginModule.eventTypes;\n\n for (var eventName in publishedEvents) {\n if (!publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName)) {\n {\n throw Error( \"EventPluginRegistry: Failed to publish event `\" + eventName + \"` for plugin `\" + pluginName + \"`.\" );\n }\n }\n }\n }\n}\n/**\n * Publishes an event so that it can be dispatched by the supplied plugin.\n *\n * @param {object} dispatchConfig Dispatch configuration for the event.\n * @param {object} PluginModule Plugin publishing the event.\n * @return {boolean} True if the event was successfully published.\n * @private\n */\n\n\nfunction publishEventForPlugin(dispatchConfig, pluginModule, eventName) {\n if (!!eventNameDispatchConfigs.hasOwnProperty(eventName)) {\n {\n throw Error( \"EventPluginRegistry: More than one plugin attempted to publish the same event name, `\" + eventName + \"`.\" );\n }\n }\n\n eventNameDispatchConfigs[eventName] = dispatchConfig;\n var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;\n\n if (phasedRegistrationNames) {\n for (var phaseName in phasedRegistrationNames) {\n if (phasedRegistrationNames.hasOwnProperty(phaseName)) {\n var phasedRegistrationName = phasedRegistrationNames[phaseName];\n publishRegistrationName(phasedRegistrationName, pluginModule, eventName);\n }\n }\n\n return true;\n } else if (dispatchConfig.registrationName) {\n publishRegistrationName(dispatchConfig.registrationName, pluginModule, eventName);\n return true;\n }\n\n return false;\n}\n/**\n * Publishes a registration name that is used to identify dispatched events.\n *\n * @param {string} registrationName Registration name to add.\n * @param {object} PluginModule Plugin publishing the event.\n * @private\n */\n\n\nfunction publishRegistrationName(registrationName, pluginModule, eventName) {\n if (!!registrationNameModules[registrationName]) {\n {\n throw Error( \"EventPluginRegistry: More than one plugin attempted to publish the same registration name, `\" + registrationName + \"`.\" );\n }\n }\n\n registrationNameModules[registrationName] = pluginModule;\n registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies;\n\n {\n var lowerCasedName = registrationName.toLowerCase();\n possibleRegistrationNames[lowerCasedName] = registrationName;\n\n if (registrationName === 'onDoubleClick') {\n possibleRegistrationNames.ondblclick = registrationName;\n }\n }\n}\n/**\n * Registers plugins so that they can extract and dispatch events.\n */\n\n/**\n * Ordered list of injected plugins.\n */\n\n\nvar plugins = [];\n/**\n * Mapping from event name to dispatch config\n */\n\nvar eventNameDispatchConfigs = {};\n/**\n * Mapping from registration name to plugin module\n */\n\nvar registrationNameModules = {};\n/**\n * Mapping from registration name to event name\n */\n\nvar registrationNameDependencies = {};\n/**\n * Mapping from lowercase registration names to the properly cased version,\n * used to warn in the case of missing event handlers. Available\n * only in true.\n * @type {Object}\n */\n\nvar possibleRegistrationNames = {} ; // Trust the developer to only use possibleRegistrationNames in true\n\n/**\n * Injects an ordering of plugins (by plugin name). This allows the ordering\n * to be decoupled from injection of the actual plugins so that ordering is\n * always deterministic regardless of packaging, on-the-fly injection, etc.\n *\n * @param {array} InjectedEventPluginOrder\n * @internal\n */\n\nfunction injectEventPluginOrder(injectedEventPluginOrder) {\n if (!!eventPluginOrder) {\n {\n throw Error( \"EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.\" );\n }\n } // Clone the ordering so it cannot be dynamically mutated.\n\n\n eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);\n recomputePluginOrdering();\n}\n/**\n * Injects plugins to be used by plugin event system. The plugin names must be\n * in the ordering injected by `injectEventPluginOrder`.\n *\n * Plugins can be injected as part of page initialization or on-the-fly.\n *\n * @param {object} injectedNamesToPlugins Map from names to plugin modules.\n * @internal\n */\n\nfunction injectEventPluginsByName(injectedNamesToPlugins) {\n var isOrderingDirty = false;\n\n for (var pluginName in injectedNamesToPlugins) {\n if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {\n continue;\n }\n\n var pluginModule = injectedNamesToPlugins[pluginName];\n\n if (!namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule) {\n if (!!namesToPlugins[pluginName]) {\n {\n throw Error( \"EventPluginRegistry: Cannot inject two different event plugins using the same name, `\" + pluginName + \"`.\" );\n }\n }\n\n namesToPlugins[pluginName] = pluginModule;\n isOrderingDirty = true;\n }\n }\n\n if (isOrderingDirty) {\n recomputePluginOrdering();\n }\n}\n\nvar canUseDOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined');\n\nvar PLUGIN_EVENT_SYSTEM = 1;\nvar IS_REPLAYED = 1 << 5;\nvar IS_FIRST_ANCESTOR = 1 << 6;\n\nvar restoreImpl = null;\nvar restoreTarget = null;\nvar restoreQueue = null;\n\nfunction restoreStateOfTarget(target) {\n // We perform this translation at the end of the event loop so that we\n // always receive the correct fiber here\n var internalInstance = getInstanceFromNode(target);\n\n if (!internalInstance) {\n // Unmounted\n return;\n }\n\n if (!(typeof restoreImpl === 'function')) {\n {\n throw Error( \"setRestoreImplementation() needs to be called to handle a target for controlled events. This error is likely caused by a bug in React. Please file an issue.\" );\n }\n }\n\n var stateNode = internalInstance.stateNode; // Guard against Fiber being unmounted.\n\n if (stateNode) {\n var _props = getFiberCurrentPropsFromNode(stateNode);\n\n restoreImpl(internalInstance.stateNode, internalInstance.type, _props);\n }\n}\n\nfunction setRestoreImplementation(impl) {\n restoreImpl = impl;\n}\nfunction enqueueStateRestore(target) {\n if (restoreTarget) {\n if (restoreQueue) {\n restoreQueue.push(target);\n } else {\n restoreQueue = [target];\n }\n } else {\n restoreTarget = target;\n }\n}\nfunction needsStateRestore() {\n return restoreTarget !== null || restoreQueue !== null;\n}\nfunction restoreStateIfNeeded() {\n if (!restoreTarget) {\n return;\n }\n\n var target = restoreTarget;\n var queuedTargets = restoreQueue;\n restoreTarget = null;\n restoreQueue = null;\n restoreStateOfTarget(target);\n\n if (queuedTargets) {\n for (var i = 0; i < queuedTargets.length; i++) {\n restoreStateOfTarget(queuedTargets[i]);\n }\n }\n}\n\nvar enableProfilerTimer = true; // Trace which interactions trigger each commit.\n\nvar enableDeprecatedFlareAPI = false; // Experimental Host Component support.\n\nvar enableFundamentalAPI = false; // Experimental Scope support.\nvar warnAboutStringRefs = false;\n\n// the renderer. Such as when we're dispatching events or if third party\n// libraries need to call batchedUpdates. Eventually, this API will go away when\n// everything is batched by default. We'll then have a similar API to opt-out of\n// scheduled work and instead do synchronous work.\n// Defaults\n\nvar batchedUpdatesImpl = function (fn, bookkeeping) {\n return fn(bookkeeping);\n};\n\nvar discreteUpdatesImpl = function (fn, a, b, c, d) {\n return fn(a, b, c, d);\n};\n\nvar flushDiscreteUpdatesImpl = function () {};\n\nvar batchedEventUpdatesImpl = batchedUpdatesImpl;\nvar isInsideEventHandler = false;\nvar isBatchingEventUpdates = false;\n\nfunction finishEventHandler() {\n // Here we wait until all updates have propagated, which is important\n // when using controlled components within layers:\n // https://github.com/facebook/react/issues/1698\n // Then we restore state of any controlled component.\n var controlledComponentsHavePendingUpdates = needsStateRestore();\n\n if (controlledComponentsHavePendingUpdates) {\n // If a controlled event was fired, we may need to restore the state of\n // the DOM node back to the controlled value. This is necessary when React\n // bails out of the update without touching the DOM.\n flushDiscreteUpdatesImpl();\n restoreStateIfNeeded();\n }\n}\n\nfunction batchedUpdates(fn, bookkeeping) {\n if (isInsideEventHandler) {\n // If we are currently inside another batch, we need to wait until it\n // fully completes before restoring state.\n return fn(bookkeeping);\n }\n\n isInsideEventHandler = true;\n\n try {\n return batchedUpdatesImpl(fn, bookkeeping);\n } finally {\n isInsideEventHandler = false;\n finishEventHandler();\n }\n}\nfunction batchedEventUpdates(fn, a, b) {\n if (isBatchingEventUpdates) {\n // If we are currently inside another batch, we need to wait until it\n // fully completes before restoring state.\n return fn(a, b);\n }\n\n isBatchingEventUpdates = true;\n\n try {\n return batchedEventUpdatesImpl(fn, a, b);\n } finally {\n isBatchingEventUpdates = false;\n finishEventHandler();\n }\n} // This is for the React Flare event system\nfunction discreteUpdates(fn, a, b, c, d) {\n var prevIsInsideEventHandler = isInsideEventHandler;\n isInsideEventHandler = true;\n\n try {\n return discreteUpdatesImpl(fn, a, b, c, d);\n } finally {\n isInsideEventHandler = prevIsInsideEventHandler;\n\n if (!isInsideEventHandler) {\n finishEventHandler();\n }\n }\n}\nfunction flushDiscreteUpdatesIfNeeded(timeStamp) {\n // event.timeStamp isn't overly reliable due to inconsistencies in\n // how different browsers have historically provided the time stamp.\n // Some browsers provide high-resolution time stamps for all events,\n // some provide low-resolution time stamps for all events. FF < 52\n // even mixes both time stamps together. Some browsers even report\n // negative time stamps or time stamps that are 0 (iOS9) in some cases.\n // Given we are only comparing two time stamps with equality (!==),\n // we are safe from the resolution differences. If the time stamp is 0\n // we bail-out of preventing the flush, which can affect semantics,\n // such as if an earlier flush removes or adds event listeners that\n // are fired in the subsequent flush. However, this is the same\n // behaviour as we had before this change, so the risks are low.\n if (!isInsideEventHandler && (!enableDeprecatedFlareAPI )) {\n flushDiscreteUpdatesImpl();\n }\n}\nfunction setBatchingImplementation(_batchedUpdatesImpl, _discreteUpdatesImpl, _flushDiscreteUpdatesImpl, _batchedEventUpdatesImpl) {\n batchedUpdatesImpl = _batchedUpdatesImpl;\n discreteUpdatesImpl = _discreteUpdatesImpl;\n flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;\n batchedEventUpdatesImpl = _batchedEventUpdatesImpl;\n}\n\nvar DiscreteEvent = 0;\nvar UserBlockingEvent = 1;\nvar ContinuousEvent = 2;\n\n// A reserved attribute.\n// It is handled by React separately and shouldn't be written to the DOM.\nvar RESERVED = 0; // A simple string attribute.\n// Attributes that aren't in the whitelist are presumed to have this type.\n\nvar STRING = 1; // A string attribute that accepts booleans in React. In HTML, these are called\n// \"enumerated\" attributes with \"true\" and \"false\" as possible values.\n// When true, it should be set to a \"true\" string.\n// When false, it should be set to a \"false\" string.\n\nvar BOOLEANISH_STRING = 2; // A real boolean attribute.\n// When true, it should be present (set either to an empty string or its name).\n// When false, it should be omitted.\n\nvar BOOLEAN = 3; // An attribute that can be used as a flag as well as with a value.\n// When true, it should be present (set either to an empty string or its name).\n// When false, it should be omitted.\n// For any other value, should be present with that value.\n\nvar OVERLOADED_BOOLEAN = 4; // An attribute that must be numeric or parse as a numeric.\n// When falsy, it should be removed.\n\nvar NUMERIC = 5; // An attribute that must be positive numeric or parse as a positive numeric.\n// When falsy, it should be removed.\n\nvar POSITIVE_NUMERIC = 6;\n\n/* eslint-disable max-len */\nvar ATTRIBUTE_NAME_START_CHAR = \":A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\";\n/* eslint-enable max-len */\n\nvar ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + \"\\\\-.0-9\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040\";\nvar ROOT_ATTRIBUTE_NAME = 'data-reactroot';\nvar VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$');\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar illegalAttributeNameCache = {};\nvar validatedAttributeNameCache = {};\nfunction isAttributeNameSafe(attributeName) {\n if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) {\n return true;\n }\n\n if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) {\n return false;\n }\n\n if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {\n validatedAttributeNameCache[attributeName] = true;\n return true;\n }\n\n illegalAttributeNameCache[attributeName] = true;\n\n {\n error('Invalid attribute name: `%s`', attributeName);\n }\n\n return false;\n}\nfunction shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) {\n if (propertyInfo !== null) {\n return propertyInfo.type === RESERVED;\n }\n\n if (isCustomComponentTag) {\n return false;\n }\n\n if (name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) {\n return true;\n }\n\n return false;\n}\nfunction shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) {\n if (propertyInfo !== null && propertyInfo.type === RESERVED) {\n return false;\n }\n\n switch (typeof value) {\n case 'function': // $FlowIssue symbol is perfectly valid here\n\n case 'symbol':\n // eslint-disable-line\n return true;\n\n case 'boolean':\n {\n if (isCustomComponentTag) {\n return false;\n }\n\n if (propertyInfo !== null) {\n return !propertyInfo.acceptsBooleans;\n } else {\n var prefix = name.toLowerCase().slice(0, 5);\n return prefix !== 'data-' && prefix !== 'aria-';\n }\n }\n\n default:\n return false;\n }\n}\nfunction shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) {\n if (value === null || typeof value === 'undefined') {\n return true;\n }\n\n if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag)) {\n return true;\n }\n\n if (isCustomComponentTag) {\n return false;\n }\n\n if (propertyInfo !== null) {\n switch (propertyInfo.type) {\n case BOOLEAN:\n return !value;\n\n case OVERLOADED_BOOLEAN:\n return value === false;\n\n case NUMERIC:\n return isNaN(value);\n\n case POSITIVE_NUMERIC:\n return isNaN(value) || value < 1;\n }\n }\n\n return false;\n}\nfunction getPropertyInfo(name) {\n return properties.hasOwnProperty(name) ? properties[name] : null;\n}\n\nfunction PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace, sanitizeURL) {\n this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN;\n this.attributeName = attributeName;\n this.attributeNamespace = attributeNamespace;\n this.mustUseProperty = mustUseProperty;\n this.propertyName = name;\n this.type = type;\n this.sanitizeURL = sanitizeURL;\n} // When adding attributes to this list, be sure to also add them to\n// the `possibleStandardNames` module to ensure casing and incorrect\n// name warnings.\n\n\nvar properties = {}; // These props are reserved by React. They shouldn't be written to the DOM.\n\nvar reservedProps = ['children', 'dangerouslySetInnerHTML', // TODO: This prevents the assignment of defaultValue to regular\n// elements (not just inputs). Now that ReactDOMInput assigns to the\n// defaultValue property -- do we need this?\n'defaultValue', 'defaultChecked', 'innerHTML', 'suppressContentEditableWarning', 'suppressHydrationWarning', 'style'];\n\nreservedProps.forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, RESERVED, false, // mustUseProperty\n name, // attributeName\n null, // attributeNamespace\n false);\n}); // A few React string attributes have a different name.\n// This is a mapping from React prop names to the attribute names.\n\n[['acceptCharset', 'accept-charset'], ['className', 'class'], ['htmlFor', 'for'], ['httpEquiv', 'http-equiv']].forEach(function (_ref) {\n var name = _ref[0],\n attributeName = _ref[1];\n properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty\n attributeName, // attributeName\n null, // attributeNamespace\n false);\n}); // These are \"enumerated\" HTML attributes that accept \"true\" and \"false\".\n// In React, we let users pass `true` and `false` even though technically\n// these aren't boolean attributes (they are coerced to strings).\n\n['contentEditable', 'draggable', 'spellCheck', 'value'].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty\n name.toLowerCase(), // attributeName\n null, // attributeNamespace\n false);\n}); // These are \"enumerated\" SVG attributes that accept \"true\" and \"false\".\n// In React, we let users pass `true` and `false` even though technically\n// these aren't boolean attributes (they are coerced to strings).\n// Since these are SVG attributes, their attribute names are case-sensitive.\n\n['autoReverse', 'externalResourcesRequired', 'focusable', 'preserveAlpha'].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty\n name, // attributeName\n null, // attributeNamespace\n false);\n}); // These are HTML boolean attributes.\n\n['allowFullScreen', 'async', // Note: there is a special case that prevents it from being written to the DOM\n// on the client side because the browsers are inconsistent. Instead we call focus().\n'autoFocus', 'autoPlay', 'controls', 'default', 'defer', 'disabled', 'disablePictureInPicture', 'formNoValidate', 'hidden', 'loop', 'noModule', 'noValidate', 'open', 'playsInline', 'readOnly', 'required', 'reversed', 'scoped', 'seamless', // Microdata\n'itemScope'].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, BOOLEAN, false, // mustUseProperty\n name.toLowerCase(), // attributeName\n null, // attributeNamespace\n false);\n}); // These are the few React props that we set as DOM properties\n// rather than attributes. These are all booleans.\n\n['checked', // Note: `option.selected` is not updated if `select.multiple` is\n// disabled with `removeAttribute`. We have special logic for handling this.\n'multiple', 'muted', 'selected' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, BOOLEAN, true, // mustUseProperty\n name, // attributeName\n null, // attributeNamespace\n false);\n}); // These are HTML attributes that are \"overloaded booleans\": they behave like\n// booleans, but can also accept a string value.\n\n['capture', 'download' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, OVERLOADED_BOOLEAN, false, // mustUseProperty\n name, // attributeName\n null, // attributeNamespace\n false);\n}); // These are HTML attributes that must be positive numbers.\n\n['cols', 'rows', 'size', 'span' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, POSITIVE_NUMERIC, false, // mustUseProperty\n name, // attributeName\n null, // attributeNamespace\n false);\n}); // These are HTML attributes that must be numbers.\n\n['rowSpan', 'start'].forEach(function (name) {\n properties[name] = new PropertyInfoRecord(name, NUMERIC, false, // mustUseProperty\n name.toLowerCase(), // attributeName\n null, // attributeNamespace\n false);\n});\nvar CAMELIZE = /[\\-\\:]([a-z])/g;\n\nvar capitalize = function (token) {\n return token[1].toUpperCase();\n}; // This is a list of all SVG attributes that need special casing, namespacing,\n// or boolean value assignment. Regular attributes that just accept strings\n// and have the same names are omitted, just like in the HTML whitelist.\n// Some of these attributes can be hard to find. This list was created by\n// scraping the MDN documentation.\n\n\n['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'xmlns:xlink', 'x-height' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (attributeName) {\n var name = attributeName.replace(CAMELIZE, capitalize);\n properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty\n attributeName, null, // attributeNamespace\n false);\n}); // String SVG attributes with the xlink namespace.\n\n['xlink:actuate', 'xlink:arcrole', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (attributeName) {\n var name = attributeName.replace(CAMELIZE, capitalize);\n properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty\n attributeName, 'http://www.w3.org/1999/xlink', false);\n}); // String SVG attributes with the xml namespace.\n\n['xml:base', 'xml:lang', 'xml:space' // NOTE: if you add a camelCased prop to this list,\n// you'll need to set attributeName to name.toLowerCase()\n// instead in the assignment below.\n].forEach(function (attributeName) {\n var name = attributeName.replace(CAMELIZE, capitalize);\n properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty\n attributeName, 'http://www.w3.org/XML/1998/namespace', false);\n}); // These attribute exists both in HTML and SVG.\n// The attribute name is case-sensitive in SVG so we can't just use\n// the React name like we do for attributes that exist only in HTML.\n\n['tabIndex', 'crossOrigin'].forEach(function (attributeName) {\n properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty\n attributeName.toLowerCase(), // attributeName\n null, // attributeNamespace\n false);\n}); // These attributes accept URLs. These must not allow javascript: URLS.\n// These will also need to accept Trusted Types object in the future.\n\nvar xlinkHref = 'xlinkHref';\nproperties[xlinkHref] = new PropertyInfoRecord('xlinkHref', STRING, false, // mustUseProperty\n'xlink:href', 'http://www.w3.org/1999/xlink', true);\n['src', 'href', 'action', 'formAction'].forEach(function (attributeName) {\n properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty\n attributeName.toLowerCase(), // attributeName\n null, // attributeNamespace\n true);\n});\n\nvar ReactDebugCurrentFrame = null;\n\n{\n ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n} // A javascript: URL can contain leading C0 control or \\u0020 SPACE,\n// and any newline or tab are filtered out as if they're not part of the URL.\n// https://url.spec.whatwg.org/#url-parsing\n// Tab or newline are defined as \\r\\n\\t:\n// https://infra.spec.whatwg.org/#ascii-tab-or-newline\n// A C0 control is a code point in the range \\u0000 NULL to \\u001F\n// INFORMATION SEPARATOR ONE, inclusive:\n// https://infra.spec.whatwg.org/#c0-control-or-space\n\n/* eslint-disable max-len */\n\n\nvar isJavaScriptProtocol = /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*\\:/i;\nvar didWarn = false;\n\nfunction sanitizeURL(url) {\n {\n if (!didWarn && isJavaScriptProtocol.test(url)) {\n didWarn = true;\n\n error('A future version of React will block javascript: URLs as a security precaution. ' + 'Use event handlers instead if you can. If you need to generate unsafe HTML try ' + 'using dangerouslySetInnerHTML instead. React was passed %s.', JSON.stringify(url));\n }\n }\n}\n\n/**\n * Get the value for a property on a node. Only used in DEV for SSR validation.\n * The \"expected\" argument is used as a hint of what the expected value is.\n * Some properties have multiple equivalent values.\n */\nfunction getValueForProperty(node, name, expected, propertyInfo) {\n {\n if (propertyInfo.mustUseProperty) {\n var propertyName = propertyInfo.propertyName;\n return node[propertyName];\n } else {\n if ( propertyInfo.sanitizeURL) {\n // If we haven't fully disabled javascript: URLs, and if\n // the hydration is successful of a javascript: URL, we\n // still want to warn on the client.\n sanitizeURL('' + expected);\n }\n\n var attributeName = propertyInfo.attributeName;\n var stringValue = null;\n\n if (propertyInfo.type === OVERLOADED_BOOLEAN) {\n if (node.hasAttribute(attributeName)) {\n var value = node.getAttribute(attributeName);\n\n if (value === '') {\n return true;\n }\n\n if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {\n return value;\n }\n\n if (value === '' + expected) {\n return expected;\n }\n\n return value;\n }\n } else if (node.hasAttribute(attributeName)) {\n if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {\n // We had an attribute but shouldn't have had one, so read it\n // for the error message.\n return node.getAttribute(attributeName);\n }\n\n if (propertyInfo.type === BOOLEAN) {\n // If this was a boolean, it doesn't matter what the value is\n // the fact that we have it is the same as the expected.\n return expected;\n } // Even if this property uses a namespace we use getAttribute\n // because we assume its namespaced name is the same as our config.\n // To use getAttributeNS we need the local name which we don't have\n // in our config atm.\n\n\n stringValue = node.getAttribute(attributeName);\n }\n\n if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {\n return stringValue === null ? expected : stringValue;\n } else if (stringValue === '' + expected) {\n return expected;\n } else {\n return stringValue;\n }\n }\n }\n}\n/**\n * Get the value for a attribute on a node. Only used in DEV for SSR validation.\n * The third argument is used as a hint of what the expected value is. Some\n * attributes have multiple equivalent values.\n */\n\nfunction getValueForAttribute(node, name, expected) {\n {\n if (!isAttributeNameSafe(name)) {\n return;\n }\n\n if (!node.hasAttribute(name)) {\n return expected === undefined ? undefined : null;\n }\n\n var value = node.getAttribute(name);\n\n if (value === '' + expected) {\n return expected;\n }\n\n return value;\n }\n}\n/**\n * Sets the value for a property on a node.\n *\n * @param {DOMElement} node\n * @param {string} name\n * @param {*} value\n */\n\nfunction setValueForProperty(node, name, value, isCustomComponentTag) {\n var propertyInfo = getPropertyInfo(name);\n\n if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {\n return;\n }\n\n if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) {\n value = null;\n } // If the prop isn't in the special list, treat it as a simple attribute.\n\n\n if (isCustomComponentTag || propertyInfo === null) {\n if (isAttributeNameSafe(name)) {\n var _attributeName = name;\n\n if (value === null) {\n node.removeAttribute(_attributeName);\n } else {\n node.setAttribute(_attributeName, '' + value);\n }\n }\n\n return;\n }\n\n var mustUseProperty = propertyInfo.mustUseProperty;\n\n if (mustUseProperty) {\n var propertyName = propertyInfo.propertyName;\n\n if (value === null) {\n var type = propertyInfo.type;\n node[propertyName] = type === BOOLEAN ? false : '';\n } else {\n // Contrary to `setAttribute`, object properties are properly\n // `toString`ed by IE8/9.\n node[propertyName] = value;\n }\n\n return;\n } // The rest are treated as attributes with special cases.\n\n\n var attributeName = propertyInfo.attributeName,\n attributeNamespace = propertyInfo.attributeNamespace;\n\n if (value === null) {\n node.removeAttribute(attributeName);\n } else {\n var _type = propertyInfo.type;\n var attributeValue;\n\n if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) {\n // If attribute type is boolean, we know for sure it won't be an execution sink\n // and we won't require Trusted Type here.\n attributeValue = '';\n } else {\n // `setAttribute` with objects becomes only `[object]` in IE8/9,\n // ('' + value) makes it output the correct toString()-value.\n {\n attributeValue = '' + value;\n }\n\n if (propertyInfo.sanitizeURL) {\n sanitizeURL(attributeValue.toString());\n }\n }\n\n if (attributeNamespace) {\n node.setAttributeNS(attributeNamespace, attributeName, attributeValue);\n } else {\n node.setAttribute(attributeName, attributeValue);\n }\n }\n}\n\nvar BEFORE_SLASH_RE = /^(.*)[\\\\\\/]/;\nfunction describeComponentFrame (name, source, ownerName) {\n var sourceInfo = '';\n\n if (source) {\n var path = source.fileName;\n var fileName = path.replace(BEFORE_SLASH_RE, '');\n\n {\n // In DEV, include code for a common special case:\n // prefer \"folder/index.js\" instead of just \"index.js\".\n if (/^index\\./.test(fileName)) {\n var match = path.match(BEFORE_SLASH_RE);\n\n if (match) {\n var pathBeforeSlash = match[1];\n\n if (pathBeforeSlash) {\n var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');\n fileName = folderName + '/' + fileName;\n }\n }\n }\n }\n\n sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';\n } else if (ownerName) {\n sourceInfo = ' (created by ' + ownerName + ')';\n }\n\n return '\\n in ' + (name || 'Unknown') + sourceInfo;\n}\n\n// The Symbol used to tag the ReactElement-like types. If there is no native Symbol\n// nor polyfill, then a plain number is used for performance.\nvar hasSymbol = typeof Symbol === 'function' && Symbol.for;\nvar REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;\nvar REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;\nvar REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;\nvar REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;\nvar REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;\nvar REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;\nvar REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace; // TODO: We don't use AsyncMode or ConcurrentMode anymore. They were temporary\nvar REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;\nvar REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;\nvar REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;\nvar REACT_SUSPENSE_LIST_TYPE = hasSymbol ? Symbol.for('react.suspense_list') : 0xead8;\nvar REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;\nvar REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;\nvar REACT_BLOCK_TYPE = hasSymbol ? Symbol.for('react.block') : 0xead9;\nvar MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\nvar FAUX_ITERATOR_SYMBOL = '@@iterator';\nfunction getIteratorFn(maybeIterable) {\n if (maybeIterable === null || typeof maybeIterable !== 'object') {\n return null;\n }\n\n var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];\n\n if (typeof maybeIterator === 'function') {\n return maybeIterator;\n }\n\n return null;\n}\n\nvar Uninitialized = -1;\nvar Pending = 0;\nvar Resolved = 1;\nvar Rejected = 2;\nfunction refineResolvedLazyComponent(lazyComponent) {\n return lazyComponent._status === Resolved ? lazyComponent._result : null;\n}\nfunction initializeLazyComponentType(lazyComponent) {\n if (lazyComponent._status === Uninitialized) {\n lazyComponent._status = Pending;\n var ctor = lazyComponent._ctor;\n var thenable = ctor();\n lazyComponent._result = thenable;\n thenable.then(function (moduleObject) {\n if (lazyComponent._status === Pending) {\n var defaultExport = moduleObject.default;\n\n {\n if (defaultExport === undefined) {\n error('lazy: Expected the result of a dynamic import() call. ' + 'Instead received: %s\\n\\nYour code should look like: \\n ' + \"const MyComponent = lazy(() => import('./MyComponent'))\", moduleObject);\n }\n }\n\n lazyComponent._status = Resolved;\n lazyComponent._result = defaultExport;\n }\n }, function (error) {\n if (lazyComponent._status === Pending) {\n lazyComponent._status = Rejected;\n lazyComponent._result = error;\n }\n });\n }\n}\n\nfunction getWrappedName(outerType, innerType, wrapperName) {\n var functionName = innerType.displayName || innerType.name || '';\n return outerType.displayName || (functionName !== '' ? wrapperName + \"(\" + functionName + \")\" : wrapperName);\n}\n\nfunction getComponentName(type) {\n if (type == null) {\n // Host root, text node or just invalid type.\n return null;\n }\n\n {\n if (typeof type.tag === 'number') {\n error('Received an unexpected object in getComponentName(). ' + 'This is likely a bug in React. Please file an issue.');\n }\n }\n\n if (typeof type === 'function') {\n return type.displayName || type.name || null;\n }\n\n if (typeof type === 'string') {\n return type;\n }\n\n switch (type) {\n case REACT_FRAGMENT_TYPE:\n return 'Fragment';\n\n case REACT_PORTAL_TYPE:\n return 'Portal';\n\n case REACT_PROFILER_TYPE:\n return \"Profiler\";\n\n case REACT_STRICT_MODE_TYPE:\n return 'StrictMode';\n\n case REACT_SUSPENSE_TYPE:\n return 'Suspense';\n\n case REACT_SUSPENSE_LIST_TYPE:\n return 'SuspenseList';\n }\n\n if (typeof type === 'object') {\n switch (type.$$typeof) {\n case REACT_CONTEXT_TYPE:\n return 'Context.Consumer';\n\n case REACT_PROVIDER_TYPE:\n return 'Context.Provider';\n\n case REACT_FORWARD_REF_TYPE:\n return getWrappedName(type, type.render, 'ForwardRef');\n\n case REACT_MEMO_TYPE:\n return getComponentName(type.type);\n\n case REACT_BLOCK_TYPE:\n return getComponentName(type.render);\n\n case REACT_LAZY_TYPE:\n {\n var thenable = type;\n var resolvedThenable = refineResolvedLazyComponent(thenable);\n\n if (resolvedThenable) {\n return getComponentName(resolvedThenable);\n }\n\n break;\n }\n }\n }\n\n return null;\n}\n\nvar ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;\n\nfunction describeFiber(fiber) {\n switch (fiber.tag) {\n case HostRoot:\n case HostPortal:\n case HostText:\n case Fragment:\n case ContextProvider:\n case ContextConsumer:\n return '';\n\n default:\n var owner = fiber._debugOwner;\n var source = fiber._debugSource;\n var name = getComponentName(fiber.type);\n var ownerName = null;\n\n if (owner) {\n ownerName = getComponentName(owner.type);\n }\n\n return describeComponentFrame(name, source, ownerName);\n }\n}\n\nfunction getStackByFiberInDevAndProd(workInProgress) {\n var info = '';\n var node = workInProgress;\n\n do {\n info += describeFiber(node);\n node = node.return;\n } while (node);\n\n return info;\n}\nvar current = null;\nvar isRendering = false;\nfunction getCurrentFiberOwnerNameInDevOrNull() {\n {\n if (current === null) {\n return null;\n }\n\n var owner = current._debugOwner;\n\n if (owner !== null && typeof owner !== 'undefined') {\n return getComponentName(owner.type);\n }\n }\n\n return null;\n}\nfunction getCurrentFiberStackInDev() {\n {\n if (current === null) {\n return '';\n } // Safe because if current fiber exists, we are reconciling,\n // and it is guaranteed to be the work-in-progress version.\n\n\n return getStackByFiberInDevAndProd(current);\n }\n}\nfunction resetCurrentFiber() {\n {\n ReactDebugCurrentFrame$1.getCurrentStack = null;\n current = null;\n isRendering = false;\n }\n}\nfunction setCurrentFiber(fiber) {\n {\n ReactDebugCurrentFrame$1.getCurrentStack = getCurrentFiberStackInDev;\n current = fiber;\n isRendering = false;\n }\n}\nfunction setIsRendering(rendering) {\n {\n isRendering = rendering;\n }\n}\n\n// Flow does not allow string concatenation of most non-string types. To work\n// around this limitation, we use an opaque type that can only be obtained by\n// passing the value through getToStringValue first.\nfunction toString(value) {\n return '' + value;\n}\nfunction getToStringValue(value) {\n switch (typeof value) {\n case 'boolean':\n case 'number':\n case 'object':\n case 'string':\n case 'undefined':\n return value;\n\n default:\n // function, symbol are assigned as empty strings\n return '';\n }\n}\n\nvar ReactDebugCurrentFrame$2 = null;\nvar ReactControlledValuePropTypes = {\n checkPropTypes: null\n};\n\n{\n ReactDebugCurrentFrame$2 = ReactSharedInternals.ReactDebugCurrentFrame;\n var hasReadOnlyValue = {\n button: true,\n checkbox: true,\n image: true,\n hidden: true,\n radio: true,\n reset: true,\n submit: true\n };\n var propTypes = {\n value: function (props, propName, componentName) {\n if (hasReadOnlyValue[props.type] || props.onChange || props.readOnly || props.disabled || props[propName] == null || enableDeprecatedFlareAPI ) {\n return null;\n }\n\n return new Error('You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.');\n },\n checked: function (props, propName, componentName) {\n if (props.onChange || props.readOnly || props.disabled || props[propName] == null || enableDeprecatedFlareAPI ) {\n return null;\n }\n\n return new Error('You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.');\n }\n };\n /**\n * Provide a linked `value` attribute for controlled forms. You should not use\n * this outside of the ReactDOM controlled form components.\n */\n\n ReactControlledValuePropTypes.checkPropTypes = function (tagName, props) {\n checkPropTypes(propTypes, props, 'prop', tagName, ReactDebugCurrentFrame$2.getStackAddendum);\n };\n}\n\nfunction isCheckable(elem) {\n var type = elem.type;\n var nodeName = elem.nodeName;\n return nodeName && nodeName.toLowerCase() === 'input' && (type === 'checkbox' || type === 'radio');\n}\n\nfunction getTracker(node) {\n return node._valueTracker;\n}\n\nfunction detachTracker(node) {\n node._valueTracker = null;\n}\n\nfunction getValueFromNode(node) {\n var value = '';\n\n if (!node) {\n return value;\n }\n\n if (isCheckable(node)) {\n value = node.checked ? 'true' : 'false';\n } else {\n value = node.value;\n }\n\n return value;\n}\n\nfunction trackValueOnNode(node) {\n var valueField = isCheckable(node) ? 'checked' : 'value';\n var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);\n var currentValue = '' + node[valueField]; // if someone has already defined a value or Safari, then bail\n // and don't track value will cause over reporting of changes,\n // but it's better then a hard failure\n // (needed for certain tests that spyOn input values and Safari)\n\n if (node.hasOwnProperty(valueField) || typeof descriptor === 'undefined' || typeof descriptor.get !== 'function' || typeof descriptor.set !== 'function') {\n return;\n }\n\n var get = descriptor.get,\n set = descriptor.set;\n Object.defineProperty(node, valueField, {\n configurable: true,\n get: function () {\n return get.call(this);\n },\n set: function (value) {\n currentValue = '' + value;\n set.call(this, value);\n }\n }); // We could've passed this the first time\n // but it triggers a bug in IE11 and Edge 14/15.\n // Calling defineProperty() again should be equivalent.\n // https://github.com/facebook/react/issues/11768\n\n Object.defineProperty(node, valueField, {\n enumerable: descriptor.enumerable\n });\n var tracker = {\n getValue: function () {\n return currentValue;\n },\n setValue: function (value) {\n currentValue = '' + value;\n },\n stopTracking: function () {\n detachTracker(node);\n delete node[valueField];\n }\n };\n return tracker;\n}\n\nfunction track(node) {\n if (getTracker(node)) {\n return;\n } // TODO: Once it's just Fiber we can move this to node._wrapperState\n\n\n node._valueTracker = trackValueOnNode(node);\n}\nfunction updateValueIfChanged(node) {\n if (!node) {\n return false;\n }\n\n var tracker = getTracker(node); // if there is no tracker at this point it's unlikely\n // that trying again will succeed\n\n if (!tracker) {\n return true;\n }\n\n var lastValue = tracker.getValue();\n var nextValue = getValueFromNode(node);\n\n if (nextValue !== lastValue) {\n tracker.setValue(nextValue);\n return true;\n }\n\n return false;\n}\n\nvar didWarnValueDefaultValue = false;\nvar didWarnCheckedDefaultChecked = false;\nvar didWarnControlledToUncontrolled = false;\nvar didWarnUncontrolledToControlled = false;\n\nfunction isControlled(props) {\n var usesChecked = props.type === 'checkbox' || props.type === 'radio';\n return usesChecked ? props.checked != null : props.value != null;\n}\n/**\n * Implements an host component that allows setting these optional\n * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.\n *\n * If `checked` or `value` are not supplied (or null/undefined), user actions\n * that affect the checked state or value will trigger updates to the element.\n *\n * If they are supplied (and not null/undefined), the rendered element will not\n * trigger updates to the element. Instead, the props must change in order for\n * the rendered element to be updated.\n *\n * The rendered element will be initialized as unchecked (or `defaultChecked`)\n * with an empty value (or `defaultValue`).\n *\n * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html\n */\n\n\nfunction getHostProps(element, props) {\n var node = element;\n var checked = props.checked;\n\n var hostProps = _assign({}, props, {\n defaultChecked: undefined,\n defaultValue: undefined,\n value: undefined,\n checked: checked != null ? checked : node._wrapperState.initialChecked\n });\n\n return hostProps;\n}\nfunction initWrapperState(element, props) {\n {\n ReactControlledValuePropTypes.checkPropTypes('input', props);\n\n if (props.checked !== undefined && props.defaultChecked !== undefined && !didWarnCheckedDefaultChecked) {\n error('%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type);\n\n didWarnCheckedDefaultChecked = true;\n }\n\n if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) {\n error('%s contains an input of type %s with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type);\n\n didWarnValueDefaultValue = true;\n }\n }\n\n var node = element;\n var defaultValue = props.defaultValue == null ? '' : props.defaultValue;\n node._wrapperState = {\n initialChecked: props.checked != null ? props.checked : props.defaultChecked,\n initialValue: getToStringValue(props.value != null ? props.value : defaultValue),\n controlled: isControlled(props)\n };\n}\nfunction updateChecked(element, props) {\n var node = element;\n var checked = props.checked;\n\n if (checked != null) {\n setValueForProperty(node, 'checked', checked, false);\n }\n}\nfunction updateWrapper(element, props) {\n var node = element;\n\n {\n var controlled = isControlled(props);\n\n if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {\n error('A component is changing an uncontrolled input of type %s to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', props.type);\n\n didWarnUncontrolledToControlled = true;\n }\n\n if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {\n error('A component is changing a controlled input of type %s to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', props.type);\n\n didWarnControlledToUncontrolled = true;\n }\n }\n\n updateChecked(element, props);\n var value = getToStringValue(props.value);\n var type = props.type;\n\n if (value != null) {\n if (type === 'number') {\n if (value === 0 && node.value === '' || // We explicitly want to coerce to number here if possible.\n // eslint-disable-next-line\n node.value != value) {\n node.value = toString(value);\n }\n } else if (node.value !== toString(value)) {\n node.value = toString(value);\n }\n } else if (type === 'submit' || type === 'reset') {\n // Submit/reset inputs need the attribute removed completely to avoid\n // blank-text buttons.\n node.removeAttribute('value');\n return;\n }\n\n {\n // When syncing the value attribute, the value comes from a cascade of\n // properties:\n // 1. The value React property\n // 2. The defaultValue React property\n // 3. Otherwise there should be no change\n if (props.hasOwnProperty('value')) {\n setDefaultValue(node, props.type, value);\n } else if (props.hasOwnProperty('defaultValue')) {\n setDefaultValue(node, props.type, getToStringValue(props.defaultValue));\n }\n }\n\n {\n // When syncing the checked attribute, it only changes when it needs\n // to be removed, such as transitioning from a checkbox into a text input\n if (props.checked == null && props.defaultChecked != null) {\n node.defaultChecked = !!props.defaultChecked;\n }\n }\n}\nfunction postMountWrapper(element, props, isHydrating) {\n var node = element; // Do not assign value if it is already set. This prevents user text input\n // from being lost during SSR hydration.\n\n if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {\n var type = props.type;\n var isButton = type === 'submit' || type === 'reset'; // Avoid setting value attribute on submit/reset inputs as it overrides the\n // default value provided by the browser. See: #12872\n\n if (isButton && (props.value === undefined || props.value === null)) {\n return;\n }\n\n var initialValue = toString(node._wrapperState.initialValue); // Do not assign value if it is already set. This prevents user text input\n // from being lost during SSR hydration.\n\n if (!isHydrating) {\n {\n // When syncing the value attribute, the value property should use\n // the wrapperState._initialValue property. This uses:\n //\n // 1. The value React property when present\n // 2. The defaultValue React property when present\n // 3. An empty string\n if (initialValue !== node.value) {\n node.value = initialValue;\n }\n }\n }\n\n {\n // Otherwise, the value attribute is synchronized to the property,\n // so we assign defaultValue to the same thing as the value property\n // assignment step above.\n node.defaultValue = initialValue;\n }\n } // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug\n // this is needed to work around a chrome bug where setting defaultChecked\n // will sometimes influence the value of checked (even after detachment).\n // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416\n // We need to temporarily unset name to avoid disrupting radio button groups.\n\n\n var name = node.name;\n\n if (name !== '') {\n node.name = '';\n }\n\n {\n // When syncing the checked attribute, both the checked property and\n // attribute are assigned at the same time using defaultChecked. This uses:\n //\n // 1. The checked React property when present\n // 2. The defaultChecked React property when present\n // 3. Otherwise, false\n node.defaultChecked = !node.defaultChecked;\n node.defaultChecked = !!node._wrapperState.initialChecked;\n }\n\n if (name !== '') {\n node.name = name;\n }\n}\nfunction restoreControlledState(element, props) {\n var node = element;\n updateWrapper(node, props);\n updateNamedCousins(node, props);\n}\n\nfunction updateNamedCousins(rootNode, props) {\n var name = props.name;\n\n if (props.type === 'radio' && name != null) {\n var queryRoot = rootNode;\n\n while (queryRoot.parentNode) {\n queryRoot = queryRoot.parentNode;\n } // If `rootNode.form` was non-null, then we could try `form.elements`,\n // but that sometimes behaves strangely in IE8. We could also try using\n // `form.getElementsByName`, but that will only return direct children\n // and won't include inputs that use the HTML5 `form=` attribute. Since\n // the input might not even be in a form. It might not even be in the\n // document. Let's just use the local `querySelectorAll` to ensure we don't\n // miss anything.\n\n\n var group = queryRoot.querySelectorAll('input[name=' + JSON.stringify('' + name) + '][type=\"radio\"]');\n\n for (var i = 0; i < group.length; i++) {\n var otherNode = group[i];\n\n if (otherNode === rootNode || otherNode.form !== rootNode.form) {\n continue;\n } // This will throw if radio buttons rendered by different copies of React\n // and the same name are rendered into the same form (same as #1939).\n // That's probably okay; we don't support it just as we don't support\n // mixing React radio buttons with non-React ones.\n\n\n var otherProps = getFiberCurrentPropsFromNode$1(otherNode);\n\n if (!otherProps) {\n {\n throw Error( \"ReactDOMInput: Mixing React and non-React radio inputs with the same `name` is not supported.\" );\n }\n } // We need update the tracked value on the named cousin since the value\n // was changed but the input saw no event or value set\n\n\n updateValueIfChanged(otherNode); // If this is a controlled radio button group, forcing the input that\n // was previously checked to update will cause it to be come re-checked\n // as appropriate.\n\n updateWrapper(otherNode, otherProps);\n }\n }\n} // In Chrome, assigning defaultValue to certain input types triggers input validation.\n// For number inputs, the display value loses trailing decimal points. For email inputs,\n// Chrome raises \"The specified value is not a valid email address\".\n//\n// Here we check to see if the defaultValue has actually changed, avoiding these problems\n// when the user is inputting text\n//\n// https://github.com/facebook/react/issues/7253\n\n\nfunction setDefaultValue(node, type, value) {\n if ( // Focused number inputs synchronize on blur. See ChangeEventPlugin.js\n type !== 'number' || node.ownerDocument.activeElement !== node) {\n if (value == null) {\n node.defaultValue = toString(node._wrapperState.initialValue);\n } else if (node.defaultValue !== toString(value)) {\n node.defaultValue = toString(value);\n }\n }\n}\n\nvar didWarnSelectedSetOnOption = false;\nvar didWarnInvalidChild = false;\n\nfunction flattenChildren(children) {\n var content = ''; // Flatten children. We'll warn if they are invalid\n // during validateProps() which runs for hydration too.\n // Note that this would throw on non-element objects.\n // Elements are stringified (which is normally irrelevant\n // but matters for ).\n\n React.Children.forEach(children, function (child) {\n if (child == null) {\n return;\n }\n\n content += child; // Note: we don't warn about invalid children here.\n // Instead, this is done separately below so that\n // it happens during the hydration codepath too.\n });\n return content;\n}\n/**\n * Implements an