From e76a0306e552b9a9c3026df767dfb6ae10bed33e Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 3 Mar 2017 10:09:45 +0100 Subject: [PATCH 1/6] Split the HeadingCommand. --- src/heading.js | 48 ++++++++++++++------- src/headingcommand.js | 88 +++++++++++++++----------------------- src/headingengine.js | 23 +++++++--- tests/heading.js | 33 ++++++++++----- tests/headingcommand.js | 94 +++++++++++++++++++++++------------------ tests/headingengine.js | 22 +++++++--- 6 files changed, 173 insertions(+), 135 deletions(-) diff --git a/src/heading.js b/src/heading.js index 55ca31b..ed25558 100644 --- a/src/heading.js +++ b/src/heading.js @@ -8,12 +8,9 @@ */ import HeadingEngine from './headingengine'; - import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; - import Model from '@ckeditor/ckeditor5-ui/src/model'; import createListDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/list/createlistdropdown'; - import Collection from '@ckeditor/ckeditor5-utils/src/collection'; /** @@ -35,34 +32,49 @@ export default class Heading extends Plugin { */ init() { const editor = this.editor; - const command = editor.commands.get( 'heading' ); - const options = command.options; - const collection = new Collection(); + const headingEngine = editor.plugins.get( HeadingEngine ); + const commands = headingEngine.commands; + const dropdownItems = new Collection(); - // Add options to collection. - for ( const { id, label } of options ) { - collection.add( new Model( { - id, label + for ( let { name, label } of commands ) { + // Add the option to the collection. + dropdownItems.add( new Model( { + name, label } ) ); } // Create dropdown model. const dropdownModel = new Model( { withText: true, - items: collection + items: dropdownItems } ); - // Bind dropdown model to command. - dropdownModel.bind( 'isEnabled' ).to( command, 'isEnabled' ); - dropdownModel.bind( 'label' ).to( command, 'value', option => option.label ); + dropdownModel.bind( 'isEnabled' ).to( + // Bind to #isEnabled of each command... + ...getCommandsBindingTargets( commands, 'isEnabled' ), + // ...and set it true if any command #isEnabled is true. + ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) + ); + + dropdownModel.bind( 'label' ).to( + // Bind to #value of each command... + ...getCommandsBindingTargets( commands, 'value' ), + // ...and chose the label of the first one which #value is true. + ( ...areActive ) => { + const index = areActive.findIndex( value => value ); + + // If none of the commands is active, display the first one. + return commands.get( index > -1 ? index : 0 ).label; + } + ); // Register UI component. editor.ui.componentFactory.add( 'headings', ( locale ) => { const dropdown = createListDropdown( dropdownModel, locale ); // Execute command when an item from the dropdown is selected. - this.listenTo( dropdown, 'execute', ( { source: { id } } ) => { - editor.execute( 'heading', { id } ); + this.listenTo( dropdown, 'execute', ( { source: { name } } ) => { + editor.execute( name ); editor.editing.view.focus(); } ); @@ -70,3 +82,7 @@ export default class Heading extends Plugin { } ); } } + +function getCommandsBindingTargets( commands, attribute ) { + return Array.prototype.concat( ...commands.map( c => [ c, attribute ] ) ); +} diff --git a/src/headingcommand.js b/src/headingcommand.js index 90ba257..98419c6 100644 --- a/src/headingcommand.js +++ b/src/headingcommand.js @@ -9,6 +9,7 @@ import Command from '@ckeditor/ckeditor5-core/src/command/command'; import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement'; +import camelCase from '@ckeditor/ckeditor5-utils/src/lib/lodash/camelCase'; /** * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings. @@ -20,49 +21,55 @@ export default class HeadingCommand extends Command { * Creates an instance of the command. * * @param {module:core/editor/editor~Editor} editor Editor instance. - * @param {Array.} options Heading options to be used by the command instance. + * @param {module:heading/headingcommand~HeadingOption} option An option to be used by the command instance. */ - constructor( editor, options, defaultOptionId ) { + constructor( editor, option ) { super( editor ); + Object.assign( this, option ); + /** - * Heading options used by this command. + * Name of the command * * @readonly - * @member {module:heading/headingcommand~HeadingOption} + * @member {String} */ - this.options = options; + this.name = camelCase( 'heading ' + this.id ); /** - * The id of the default option among {@link #options}. + * TODO * * @readonly - * @private - * @member {module:heading/headingcommand~HeadingOption#id} + * @member {} */ - this._defaultOptionId = defaultOptionId; + this.set( 'value', false ); + + // Update current value each time changes are done on document. + this.listenTo( editor.document, 'changesDone', () => this._updateValue() ); /** - * The currently selected heading option. + * Unique identifier of the command, also element's name in the model. + * See {@link module:heading/headingcommand~HeadingOption#id}. * * @readonly - * @observable - * @member {module:heading/headingcommand~HeadingOption} #value + * @member {String} #id */ - this.set( 'value', this.defaultOption ); - // Update current value each time changes are done on document. - this.listenTo( editor.document, 'changesDone', () => this._updateValue() ); - } + /** + * Element this command creates in the view. + * See {@link module:heading/headingcommand~HeadingOption#element}. + * + * @readonly + * @member {String} #element + */ - /** - * The default option. - * - * @member {module:heading/headingcommand~HeadingOption} #defaultOption - */ - get defaultOption() { - // See https://github.com/ckeditor/ckeditor5/issues/98. - return this._getOptionById( this._defaultOptionId ); + /** + * Label of this command. + * See {@link module:heading/headingcommand~HeadingOption#label}. + * + * @readonly + * @member {String} #label + */ } /** @@ -70,16 +77,11 @@ export default class HeadingCommand extends Command { * * @protected * @param {Object} [options] Options for executed command. - * @param {String} [options.id] The identifier of the heading option that should be applied. It should be one of the - * {@link module:heading/headingcommand~HeadingOption heading options} provided to the command constructor. If this parameter is not - * provided, - * the value from {@link #defaultOption defaultOption} will be used. * @param {module:engine/model/batch~Batch} [options.batch] Batch to collect all the change steps. * New batch will be created if this option is not set. */ _doExecute( options = {} ) { - // TODO: What should happen if option is not found? - const id = options.id || this.defaultOption.id; + const id = this.id; const doc = this.editor.document; const selection = doc.selection; const startPosition = selection.getFirstPosition(); @@ -87,8 +89,6 @@ export default class HeadingCommand extends Command { // Storing selection ranges and direction to fix selection after renaming. See ckeditor5-engine#367. const ranges = [ ...selection.getRanges() ]; const isSelectionBackward = selection.isBackward; - // If current option is same as new option - toggle already applied option back to default one. - const shouldRemove = ( id === this.value.id ); // Collect elements to change option. // This implementation may not be future proof but it's satisfactory at this stage. @@ -116,16 +116,7 @@ export default class HeadingCommand extends Command { const batch = options.batch || doc.batch(); for ( let element of elements ) { - // When removing applied option. - if ( shouldRemove ) { - if ( element.name === id ) { - batch.rename( element, this.defaultOption.id ); - } - } - // When applying new option. - else { - batch.rename( element, id ); - } + batch.rename( element, id ); } // If range's selection start/end is placed directly in renamed block - we need to restore it's position @@ -134,17 +125,6 @@ export default class HeadingCommand extends Command { } ); } - /** - * Returns the option by a given ID. - * - * @private - * @param {String} id - * @returns {module:heading/headingcommand~HeadingOption} - */ - _getOptionById( id ) { - return this.options.find( item => item.id === id ) || this.defaultOption; - } - /** * Updates command's {@link #value value} based on current selection. * @@ -155,7 +135,7 @@ export default class HeadingCommand extends Command { const block = findTopmostBlock( position ); if ( block ) { - this.value = this._getOptionById( block.name ); + this.value = this.id == block.name; } } } diff --git a/src/headingengine.js b/src/headingengine.js index 764fd71..ea0aa2e 100644 --- a/src/headingengine.js +++ b/src/headingengine.js @@ -8,6 +8,7 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter'; import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; @@ -28,6 +29,14 @@ export default class HeadingEngine extends Plugin { constructor( editor ) { super( editor ); + /** + * A collection of heading commands associated with heading engine. + * + * @readonly + * @member {module:utils/collection~Collection.} + */ + this.commands = new Collection(); + editor.config.define( 'heading', { options: [ { id: 'paragraph', element: 'p', label: 'Paragraph' }, @@ -70,11 +79,12 @@ export default class HeadingEngine extends Plugin { .fromElement( option.element ) .toElement( option.id ); } - } - // Register the heading command. - const command = new HeadingCommand( editor, options, defaultOptionId ); - editor.commands.set( 'heading', command ); + // Register the heading command for this option. + const command = new HeadingCommand( editor, option ); + this.commands.add( command ); + editor.commands.set( command.name, command ); + } } /** @@ -84,7 +94,6 @@ export default class HeadingEngine extends Plugin { // If the enter command is added to the editor, alter its behavior. // Enter at the end of a heading element should create a paragraph. const editor = this.editor; - const command = editor.commands.get( 'heading' ); const enterCommand = editor.commands.get( 'enter' ); const options = this._getLocalizedOptions(); @@ -94,8 +103,8 @@ export default class HeadingEngine extends Plugin { const batch = data.batch; const isHeading = options.some( option => option.id == positionParent.name ); - if ( isHeading && positionParent.name != command.defaultOption.id && positionParent.childCount === 0 ) { - batch.rename( positionParent, command.defaultOption.id ); + if ( isHeading && positionParent.name != defaultOptionId && positionParent.childCount === 0 ) { + batch.rename( positionParent, defaultOptionId ); } } ); } diff --git a/tests/heading.js b/tests/heading.js index ea216d1..e9cf68b 100644 --- a/tests/heading.js +++ b/tests/heading.js @@ -63,44 +63,55 @@ describe( 'Heading', () => { const executeSpy = testUtils.sinon.spy( editor, 'execute' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); - dropdown.id = 'foo'; + dropdown.name = 'headingParagraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( executeSpy ); - sinon.assert.calledWithExactly( executeSpy, 'heading', { id: 'foo' } ); + sinon.assert.calledWithExactly( executeSpy, 'headingParagraph' ); } ); it( 'should focus view after command execution', () => { const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); + dropdown.name = 'headingParagraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( focusSpy ); } ); describe( 'model to command binding', () => { - let command; + let commands; beforeEach( () => { - command = editor.commands.get( 'heading' ); + commands = editor.plugins.get( HeadingEngine ).commands; } ); it( 'isEnabled', () => { - expect( dropdown.buttonView.isEnabled ).to.be.true; - command.isEnabled = false; + for ( let command of commands ) { + command.isEnabled = false; + } + expect( dropdown.buttonView.isEnabled ).to.be.false; + + commands.get( 'heading2' ).isEnabled = true; + expect( dropdown.buttonView.isEnabled ).to.be.true; } ); it( 'label', () => { + for ( let command of commands ) { + command.value = false; + } + expect( dropdown.buttonView.label ).to.equal( 'Paragraph' ); - command.value = command.options[ 1 ]; - expect( dropdown.buttonView.label ).to.equal( 'Heading 1' ); + + commands.get( 'heading2' ).value = true; + expect( dropdown.buttonView.label ).to.equal( 'Heading 2' ); } ); } ); describe( 'localization', () => { - let command; + let commands; beforeEach( () => { const editorElement = document.createElement( 'div' ); @@ -120,7 +131,7 @@ describe( 'Heading', () => { .then( newEditor => { editor = newEditor; dropdown = editor.ui.componentFactory.create( 'headings' ); - command = editor.commands.get( 'heading' ); + commands = editor.plugins.get( HeadingEngine ).commands; } ); } ); @@ -136,7 +147,7 @@ describe( 'Heading', () => { const buttonView = dropdown.buttonView; expect( buttonView.label ).to.equal( 'Akapit' ); - command.value = command.options[ 1 ]; + commands.get( 'heading1' ).value = true; expect( buttonView.label ).to.equal( 'Nagłówek 1' ); } ); diff --git a/tests/headingcommand.js b/tests/headingcommand.js index 821eb0b..80c024c 100644 --- a/tests/headingcommand.js +++ b/tests/headingcommand.js @@ -9,23 +9,24 @@ import Range from '@ckeditor/ckeditor5-engine/src/model/range'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; const options = [ - { id: 'paragraph', element: 'p' }, - { id: 'heading1', element: 'h2' }, - { id: 'heading2', element: 'h3' }, - { id: 'heading3', element: 'h4' } + { id: 'paragraph', element: 'p', label: 'P' }, + { id: 'heading1', element: 'h2', label: 'H2' }, + { id: 'heading2', element: 'h3', label: 'H3' }, + { id: 'heading3', element: 'h4', label: 'H4' } ]; describe( 'HeadingCommand', () => { - let editor, document, command, root, schema; + let editor, document, commands, root, schema; beforeEach( () => { return ModelTestEditor.create().then( newEditor => { editor = newEditor; document = editor.document; - command = new HeadingCommand( editor, options, 'paragraph' ); + commands = {}; schema = document.schema; for ( let option of options ) { + commands[ option.id ] = new HeadingCommand( editor, option ); schema.registerItem( option.id, '$block' ); } @@ -34,7 +35,23 @@ describe( 'HeadingCommand', () => { } ); afterEach( () => { - command.destroy(); + for ( let id in commands ) { + commands[ id ].destroy(); + } + } ); + + describe( 'basic properties', () => { + for ( let option of options ) { + test( option ); + } + + function test( { id, element, label } ) { + it( `are set for option.id = ${ id }`, () => { + expect( commands[ id ].id ).to.equal( id ); + expect( commands[ id ].element ).to.equal( element ); + expect( commands[ id ].label ).to.equal( label ); + } ); + } } ); describe( 'value', () => { @@ -42,40 +59,33 @@ describe( 'HeadingCommand', () => { test( option ); } - function test( option ) { - it( `equals ${ option.id } when collapsed selection is placed inside ${ option.id } element`, () => { - setData( document, `<${ option.id }>foobar` ); + function test( { id } ) { + it( `equals ${ id } when collapsed selection is placed inside ${ id } element`, () => { + setData( document, `<${ id }>foobar` ); const element = root.getChild( 0 ); document.selection.addRange( Range.createFromParentsAndOffsets( element, 3, element, 3 ) ); - expect( command.value ).to.equal( option ); + expect( commands[ id ].value ).to.be.true; } ); } - - it( 'should be equal to #defaultOption if option has not been found', () => { - schema.registerItem( 'div', '$block' ); - setData( document, '
xyz
' ); - const element = root.getChild( 0 ); - document.selection.addRange( Range.createFromParentsAndOffsets( element, 1, element, 1 ) ); - - expect( command.value ).to.equal( command.defaultOption ); - } ); } ); describe( '_doExecute', () => { it( 'should update value after execution', () => { + const command = commands.heading1; + setData( document, '[]' ); - command._doExecute( { id: 'heading1' } ); + command._doExecute(); expect( getData( document ) ).to.equal( '[]' ); - expect( command.value ).to.be.object; - expect( command.value.id ).to.equal( 'heading1' ); - expect( command.value.element ).to.equal( 'h2' ); + expect( command.value ).to.be.true; } ); describe( 'custom options', () => { it( 'should use provided batch', () => { const batch = editor.document.batch(); + const command = commands.heading1; + setData( document, 'foo[]bar' ); expect( batch.deltas.length ).to.equal( 0 ); @@ -96,24 +106,26 @@ describe( 'HeadingCommand', () => { it( 'uses paragraph as default value', () => { setData( document, 'foo[]bar' ); - command._doExecute(); + commands.paragraph._doExecute(); expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); - it( 'converts to default option when executed with already applied option', () => { - setData( document, 'foo[]bar' ); - command._doExecute( { id: 'heading1' } ); + // it( 'converts to default option when executed with already applied option', () => { + // const command = commands.paragraph; - expect( getData( document ) ).to.equal( 'foo[]bar' ); - } ); + // setData( document, 'foo[]bar' ); + // command._doExecute( { id: 'heading1' } ); + + // expect( getData( document ) ).to.equal( 'foo[]bar' ); + // } ); it( 'converts topmost blocks', () => { schema.registerItem( 'inlineImage', '$inline' ); schema.allow( { name: '$text', inside: 'inlineImage' } ); setData( document, 'foo[]bar' ); - command._doExecute( { id: 'heading1' } ); + commands.paragraph._doExecute(); expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); @@ -121,7 +133,7 @@ describe( 'HeadingCommand', () => { function test( from, to ) { it( `converts ${ from.id } to ${ to.id } on collapsed selection`, () => { setData( document, `<${ from.id }>foo[]bar` ); - command._doExecute( { id: to.id } ); + commands[ to.id ]._doExecute(); expect( getData( document ) ).to.equal( `<${ to.id }>foo[]bar` ); } ); @@ -138,26 +150,26 @@ describe( 'HeadingCommand', () => { it( 'converts all elements where selection is applied', () => { setData( document, 'foo[bar]baz' ); - command._doExecute( { id: 'paragraph' } ); + commands.paragraph._doExecute(); expect( getData( document ) ).to.equal( 'foo[bar]baz' ); } ); - it( 'resets to default value all elements with same option', () => { - setData( document, 'foo[barbaz]' ); - command._doExecute( { id: 'heading1' } ); + // it( 'resets to default value all elements with same option', () => { + // setData( document, 'foo[barbaz]' ); + // commands.heading1._doExecute(); - expect( getData( document ) ).to.equal( - 'foo[barbaz]' - ); - } ); + // expect( getData( document ) ).to.equal( + // 'foo[barbaz]' + // ); + // } ); function test( from, to ) { it( `converts ${ from.id } to ${ to.id } on non-collapsed selection`, () => { setData( document, `<${ from.id }>foo[bar<${ from.id }>baz]qux` ); - command._doExecute( { id: to.id } ); + commands[ to.id ]._doExecute(); expect( getData( document ) ).to.equal( `<${ to.id }>foo[bar<${ to.id }>baz]qux` ); } ); diff --git a/tests/headingengine.js b/tests/headingengine.js index 6a3ec24..4b3adfe 100644 --- a/tests/headingengine.js +++ b/tests/headingengine.js @@ -54,11 +54,11 @@ describe( 'HeadingEngine', () => { expect( document.schema.check( { name: '$inline', inside: 'heading3' } ) ).to.be.true; } ); - it( 'should register option command', () => { - expect( editor.commands.has( 'heading' ) ).to.be.true; - const command = editor.commands.get( 'heading' ); - - expect( command ).to.be.instanceOf( HeadingCommand ); + it( 'should register #commands', () => { + expect( editor.commands.get( 'headingParagraph' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'headingHeading1' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'headingHeading2' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'headingHeading3' ) ).to.be.instanceOf( HeadingCommand ); } ); it( 'should convert heading1', () => { @@ -101,6 +101,12 @@ describe( 'HeadingEngine', () => { expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); + it( 'should not blow up if there\'s no enter command in the editor', () => { + return VirtualTestEditor.create( { + plugins: [ HeadingEngine ] + } ); + } ); + describe( 'config', () => { describe( 'options', () => { describe( 'default value', () => { @@ -129,7 +135,11 @@ describe( 'HeadingEngine', () => { .then( editor => { document = editor.document; - expect( editor.commands.get( 'heading' ).options ).to.deep.equal( options ); + const commands = editor.plugins.get( HeadingEngine ).commands; + + expect( commands ).to.have.length( 2 ); + expect( commands.get( 0 ).id ).to.equal( options[ 0 ].id ); + expect( commands.get( 1 ).id ).to.equal( options[ 1 ].id ); expect( document.schema.hasItem( 'paragraph' ) ).to.be.true; expect( document.schema.hasItem( 'h4' ) ).to.be.true; From 811e3d7b67f981eff7b68676ff2a7b267a9fe1b9 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 6 Mar 2017 16:13:39 +0100 Subject: [PATCH 2/6] Integration with ParagraphCommand. Use getSelectedBlocks() in HeadingCommand. Re-enabled command toggle. Renames in config.heading.options. --- src/heading.js | 30 +++++++---- src/headingcommand.js | 117 +++++++++++++--------------------------- src/headingengine.js | 46 ++++++++-------- tests/heading.js | 18 +++---- tests/headingcommand.js | 113 +++++++++++++++++++++----------------- tests/headingengine.js | 23 ++++---- 6 files changed, 167 insertions(+), 180 deletions(-) diff --git a/src/heading.js b/src/heading.js index ed25558..8a33f51 100644 --- a/src/heading.js +++ b/src/heading.js @@ -7,6 +7,8 @@ * @module heading/heading */ +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import HeadingCommand from './headingcommand'; import HeadingEngine from './headingengine'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Model from '@ckeditor/ckeditor5-ui/src/model'; @@ -24,7 +26,7 @@ export default class Heading extends Plugin { * @inheritDoc */ static get requires() { - return [ HeadingEngine ]; + return [ Paragraph, HeadingEngine ]; } /** @@ -35,12 +37,22 @@ export default class Heading extends Plugin { const headingEngine = editor.plugins.get( HeadingEngine ); const commands = headingEngine.commands; const dropdownItems = new Collection(); + let defaultCommand; + + for ( let command of commands ) { + let modelElement, title; + + if ( command instanceof HeadingCommand ) { + modelElement = command.modelElement; + } else { + modelElement = 'paragraph'; + defaultCommand = command; + } + + title = command.title; - for ( let { name, label } of commands ) { // Add the option to the collection. - dropdownItems.add( new Model( { - name, label - } ) ); + dropdownItems.add( new Model( { modelElement, label: title } ) ); } // Create dropdown model. @@ -59,12 +71,12 @@ export default class Heading extends Plugin { dropdownModel.bind( 'label' ).to( // Bind to #value of each command... ...getCommandsBindingTargets( commands, 'value' ), - // ...and chose the label of the first one which #value is true. + // ...and chose the title of the first one which #value is true. ( ...areActive ) => { const index = areActive.findIndex( value => value ); // If none of the commands is active, display the first one. - return commands.get( index > -1 ? index : 0 ).label; + return index > -1 ? commands.get( index ).title : defaultCommand.title; } ); @@ -73,8 +85,8 @@ export default class Heading extends Plugin { const dropdown = createListDropdown( dropdownModel, locale ); // Execute command when an item from the dropdown is selected. - this.listenTo( dropdown, 'execute', ( { source: { name } } ) => { - editor.execute( name ); + this.listenTo( dropdown, 'execute', ( { source: { modelElement } } ) => { + editor.execute( modelElement ); editor.editing.view.focus(); } ); diff --git a/src/headingcommand.js b/src/headingcommand.js index 98419c6..d8311a9 100644 --- a/src/headingcommand.js +++ b/src/headingcommand.js @@ -8,8 +8,6 @@ */ import Command from '@ckeditor/ckeditor5-core/src/command/command'; -import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement'; -import camelCase from '@ckeditor/ckeditor5-utils/src/lib/lodash/camelCase'; /** * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings. @@ -29,18 +27,12 @@ export default class HeadingCommand extends Command { Object.assign( this, option ); /** - * Name of the command + * Value of the command, indicating whether it is applied in the context + * of current {@link module:engine/model/document~Document#selection selection}. * * @readonly - * @member {String} - */ - this.name = camelCase( 'heading ' + this.id ); - - /** - * TODO - * - * @readonly - * @member {} + * @observable + * @member {Boolean} */ this.set( 'value', false ); @@ -49,26 +41,26 @@ export default class HeadingCommand extends Command { /** * Unique identifier of the command, also element's name in the model. - * See {@link module:heading/headingcommand~HeadingOption#id}. + * See {@link module:heading/headingcommand~HeadingOption#modelElement}. * * @readonly - * @member {String} #id + * @member {String} #modelElement */ /** * Element this command creates in the view. - * See {@link module:heading/headingcommand~HeadingOption#element}. + * See {@link module:heading/headingcommand~HeadingOption#viewElement}. * * @readonly - * @member {String} #element + * @member {String} #viewElement */ /** - * Label of this command. - * See {@link module:heading/headingcommand~HeadingOption#label}. + * User-readable title of the command. + * See {@link module:heading/headingcommand~HeadingOption#title}. * * @readonly - * @member {String} #label + * @member {String} #title */ } @@ -81,47 +73,36 @@ export default class HeadingCommand extends Command { * New batch will be created if this option is not set. */ _doExecute( options = {} ) { - const id = this.id; - const doc = this.editor.document; - const selection = doc.selection; - const startPosition = selection.getFirstPosition(); - const elements = []; - // Storing selection ranges and direction to fix selection after renaming. See ckeditor5-engine#367. + const editor = this.editor; + const document = editor.document; + const selection = document.selection; const ranges = [ ...selection.getRanges() ]; const isSelectionBackward = selection.isBackward; - // Collect elements to change option. - // This implementation may not be future proof but it's satisfactory at this stage. - if ( selection.isCollapsed ) { - const block = findTopmostBlock( startPosition ); + // If current option is same as new option - toggle already applied option back to default one. + const shouldRemove = this.value; - if ( block ) { - elements.push( block ); - } - } else { - for ( let range of ranges ) { - let startBlock = findTopmostBlock( range.start ); - const endBlock = findTopmostBlock( range.end, false ); - - elements.push( startBlock ); + document.enqueueChanges( () => { + const batch = options.batch || document.batch(); - while ( startBlock !== endBlock ) { - startBlock = startBlock.nextSibling; - elements.push( startBlock ); + for ( let element of document.selection.getSelectedBlocks() ) { + // When removing applied option. + if ( shouldRemove ) { + if ( element.name === this.modelElement ) { + // Apply paragraph to the single element only instead of working + // on the entire selection. Share the batch with the paragraph command. + editor.execute( 'paragraph', { element, batch } ); + } + } + // When applying new option. + else { + batch.rename( element, this.modelElement ); } - } - } - - doc.enqueueChanges( () => { - const batch = options.batch || doc.batch(); - - for ( let element of elements ) { - batch.rename( element, id ); } // If range's selection start/end is placed directly in renamed block - we need to restore it's position // after renaming, because renaming puts new element there. - doc.selection.setRanges( ranges, isSelectionBackward ); + selection.setRanges( ranges, isSelectionBackward ); } ); } @@ -131,45 +112,19 @@ export default class HeadingCommand extends Command { * @private */ _updateValue() { - const position = this.editor.document.selection.getFirstPosition(); - const block = findTopmostBlock( position ); + const block = this.editor.document.selection.getSelectedBlocks().next().value; if ( block ) { - this.value = this.id == block.name; + this.value = this.modelElement == block.name; } } } -// Looks for the topmost element in the position's ancestor (up to an element in the root). -// -// NOTE: This method does not check the schema directly — it assumes that only block elements can be placed directly inside -// the root. -// -// @private -// @param {engine.model.Position} position -// @param {Boolean} [nodeAfter=true] When the position is placed inside the root element, this will determine if the element before -// or after a given position will be returned. -// @returns {engine.model.Element} -function findTopmostBlock( position, nodeAfter = true ) { - let parent = position.parent; - - // If position is placed inside root - get element after/before it. - if ( parent instanceof RootElement ) { - return nodeAfter ? position.nodeAfter : position.nodeBefore; - } - - while ( !( parent.parent instanceof RootElement ) ) { - parent = parent.parent; - } - - return parent; -} - /** * Heading option descriptor. * * @typedef {Object} module:heading/headingcommand~HeadingOption - * @property {String} id Option identifier. It will be used as the element's name in the model. - * @property {String} element The name of the view element that will be used to represent the model element in the view. - * @property {String} label The display name of the option. + * @property {String} modelElement Element's name in the model. + * @property {String} viewElement The name of the view element that will be used to represent the model element in the view. + * @property {String} title The user-readable title of the option. */ diff --git a/src/headingengine.js b/src/headingengine.js index ea0aa2e..a3af511 100644 --- a/src/headingengine.js +++ b/src/headingengine.js @@ -14,7 +14,7 @@ import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildv import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import HeadingCommand from './headingcommand'; -const defaultOptionId = 'paragraph'; +const defaultModelElement = 'paragraph'; /** * The headings engine feature. It handles switching between block formats – headings and paragraph. @@ -35,14 +35,14 @@ export default class HeadingEngine extends Plugin { * @readonly * @member {module:utils/collection~Collection.} */ - this.commands = new Collection(); + this.commands = new Collection( { idProperty: 'modelElement' } ); editor.config.define( 'heading', { options: [ - { id: 'paragraph', element: 'p', label: 'Paragraph' }, - { id: 'heading1', element: 'h2', label: 'Heading 1' }, - { id: 'heading2', element: 'h3', label: 'Heading 2' }, - { id: 'heading3', element: 'h4', label: 'Heading 3' } + { modelElement: 'paragraph' }, + { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' }, + { modelElement: 'heading3', viewElement: 'h4', title: 'Heading 3' } ] } ); } @@ -62,28 +62,32 @@ export default class HeadingEngine extends Plugin { const data = editor.data; const editing = editor.editing; const options = this._getLocalizedOptions(); + let command; for ( let option of options ) { // Skip paragraph - it is defined in required Paragraph feature. - if ( option.id !== defaultOptionId ) { + if ( option.modelElement !== defaultModelElement ) { // Schema. - editor.document.schema.registerItem( option.id, '$block' ); + editor.document.schema.registerItem( option.modelElement, '$block' ); // Build converter from model to view for data and editing pipelines. buildModelConverter().for( data.modelToView, editing.modelToView ) - .fromElement( option.id ) - .toElement( option.element ); + .fromElement( option.modelElement ) + .toElement( option.viewElement ); // Build converter from view to model for data pipeline. buildViewConverter().for( data.viewToModel ) - .fromElement( option.element ) - .toElement( option.id ); + .fromElement( option.viewElement ) + .toElement( option.modelElement ); + + // Register the heading command for this option. + command = new HeadingCommand( editor, option ); + editor.commands.set( command.modelElement, command ); + } else { + command = editor.commands.get( defaultModelElement ); } - // Register the heading command for this option. - const command = new HeadingCommand( editor, option ); this.commands.add( command ); - editor.commands.set( command.name, command ); } } @@ -101,10 +105,10 @@ export default class HeadingEngine extends Plugin { this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => { const positionParent = editor.document.selection.getFirstPosition().parent; const batch = data.batch; - const isHeading = options.some( option => option.id == positionParent.name ); + const isHeading = options.some( option => option.modelElement == positionParent.name ); - if ( isHeading && positionParent.name != defaultOptionId && positionParent.childCount === 0 ) { - batch.rename( positionParent, defaultOptionId ); + if ( isHeading && positionParent.name != defaultModelElement && positionParent.childCount === 0 ) { + batch.rename( positionParent, defaultModelElement ); } } ); } @@ -112,7 +116,7 @@ export default class HeadingEngine extends Plugin { /** * Returns heading options as defined in `config.heading.options` but processed to consider - * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption#label} + * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption#title} * in the correct language. * * Note: The reason behind this method is that there's no way to use {@link utils/locale~Locale#t} @@ -145,10 +149,10 @@ export default class HeadingEngine extends Plugin { */ this._cachedLocalizedOptions = editor.config.get( 'heading.options' ) .map( option => { - if ( localizedLabels[ option.label ] ) { + if ( localizedLabels[ option.title ] ) { // Clone the option to avoid altering the original `config.heading.options`. option = Object.assign( {}, option, { - label: localizedLabels[ option.label ] + title: localizedLabels[ option.title ] } ); } diff --git a/tests/heading.js b/tests/heading.js index e9cf68b..724de28 100644 --- a/tests/heading.js +++ b/tests/heading.js @@ -63,18 +63,18 @@ describe( 'Heading', () => { const executeSpy = testUtils.sinon.spy( editor, 'execute' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); - dropdown.name = 'headingParagraph'; + dropdown.modelElement = 'paragraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( executeSpy ); - sinon.assert.calledWithExactly( executeSpy, 'headingParagraph' ); + sinon.assert.calledWithExactly( executeSpy, 'paragraph' ); } ); it( 'should focus view after command execution', () => { const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); - dropdown.name = 'headingParagraph'; + dropdown.modelElement = 'paragraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( focusSpy ); @@ -122,9 +122,9 @@ describe( 'Heading', () => { lang: 'pl', heading: { options: [ - { id: 'paragraph', element: 'p', label: 'Paragraph' }, - { id: 'heading1', element: 'h2', label: 'Heading 1' }, - { id: 'heading2', element: 'h3', label: 'Not automatically localized' } + { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, + { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'Not automatically localized' } ] } } ) @@ -137,9 +137,9 @@ describe( 'Heading', () => { it( 'does not alter the original config', () => { expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [ - { id: 'paragraph', element: 'p', label: 'Paragraph' }, - { id: 'heading1', element: 'h2', label: 'Heading 1' }, - { id: 'heading2', element: 'h3', label: 'Not automatically localized' } + { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, + { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'Not automatically localized' } ] ); } ); diff --git a/tests/headingcommand.js b/tests/headingcommand.js index 80c024c..c230765 100644 --- a/tests/headingcommand.js +++ b/tests/headingcommand.js @@ -4,15 +4,15 @@ */ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; +import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand'; import HeadingCommand from '../src/headingcommand'; import Range from '@ckeditor/ckeditor5-engine/src/model/range'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; const options = [ - { id: 'paragraph', element: 'p', label: 'P' }, - { id: 'heading1', element: 'h2', label: 'H2' }, - { id: 'heading2', element: 'h3', label: 'H3' }, - { id: 'heading3', element: 'h4', label: 'H4' } + { modelElement: 'heading1', viewElement: 'h2', title: 'H2' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'H3' }, + { modelElement: 'heading3', viewElement: 'h4', title: 'H4' } ]; describe( 'HeadingCommand', () => { @@ -25,9 +25,12 @@ describe( 'HeadingCommand', () => { commands = {}; schema = document.schema; + editor.commands.set( 'paragraph', new ParagraphCommand( editor ) ); + schema.registerItem( 'paragraph', '$block' ); + for ( let option of options ) { - commands[ option.id ] = new HeadingCommand( editor, option ); - schema.registerItem( option.id, '$block' ); + commands[ option.modelElement ] = new HeadingCommand( editor, option ); + schema.registerItem( option.modelElement, '$block' ); } root = document.getRoot(); @@ -35,8 +38,8 @@ describe( 'HeadingCommand', () => { } ); afterEach( () => { - for ( let id in commands ) { - commands[ id ].destroy(); + for ( let modelElement in commands ) { + commands[ modelElement ].destroy(); } } ); @@ -45,11 +48,11 @@ describe( 'HeadingCommand', () => { test( option ); } - function test( { id, element, label } ) { - it( `are set for option.id = ${ id }`, () => { - expect( commands[ id ].id ).to.equal( id ); - expect( commands[ id ].element ).to.equal( element ); - expect( commands[ id ].label ).to.equal( label ); + function test( { modelElement, viewElement, title } ) { + it( `are set for option.modelElement = ${ modelElement }`, () => { + expect( commands[ modelElement ].modelElement ).to.equal( modelElement ); + expect( commands[ modelElement ].viewElement ).to.equal( viewElement ); + expect( commands[ modelElement ].title ).to.equal( title ); } ); } } ); @@ -59,13 +62,13 @@ describe( 'HeadingCommand', () => { test( option ); } - function test( { id } ) { - it( `equals ${ id } when collapsed selection is placed inside ${ id } element`, () => { - setData( document, `<${ id }>foobar` ); + function test( { modelElement } ) { + it( `equals ${ modelElement } when collapsed selection is placed inside ${ modelElement } element`, () => { + setData( document, `<${ modelElement }>foobar` ); const element = root.getChild( 0 ); document.selection.addRange( Range.createFromParentsAndOffsets( element, 3, element, 3 ) ); - expect( commands[ id ].value ).to.be.true; + expect( commands[ modelElement ].value ).to.be.true; } ); } } ); @@ -94,6 +97,19 @@ describe( 'HeadingCommand', () => { expect( batch.deltas.length ).to.be.above( 0 ); } ); + + it( 'should use provided batch (converting to default option)', () => { + const batch = editor.document.batch(); + const command = commands.heading1; + + setData( document, 'foo[]bar' ); + + expect( batch.deltas.length ).to.equal( 0 ); + + command._doExecute( { batch } ); + + expect( batch.deltas.length ).to.be.above( 0 ); + } ); } ); describe( 'collapsed selection', () => { @@ -104,38 +120,31 @@ describe( 'HeadingCommand', () => { convertTo = option; } - it( 'uses paragraph as default value', () => { + it( 'converts to default option when executed with already applied option', () => { + const command = commands.heading1; + setData( document, 'foo[]bar' ); - commands.paragraph._doExecute(); + command._doExecute( { modelElement: 'heading1' } ); expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); - // it( 'converts to default option when executed with already applied option', () => { - // const command = commands.paragraph; - - // setData( document, 'foo[]bar' ); - // command._doExecute( { id: 'heading1' } ); - - // expect( getData( document ) ).to.equal( 'foo[]bar' ); - // } ); - it( 'converts topmost blocks', () => { schema.registerItem( 'inlineImage', '$inline' ); schema.allow( { name: '$text', inside: 'inlineImage' } ); - setData( document, 'foo[]bar' ); - commands.paragraph._doExecute(); + setData( document, 'foo[]bar' ); + commands.heading1._doExecute(); - expect( getData( document ) ).to.equal( 'foo[]bar' ); + expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); function test( from, to ) { - it( `converts ${ from.id } to ${ to.id } on collapsed selection`, () => { - setData( document, `<${ from.id }>foo[]bar` ); - commands[ to.id ]._doExecute(); + it( `converts ${ from.modelElement } to ${ to.modelElement } on collapsed selection`, () => { + setData( document, `<${ from.modelElement }>foo[]bar` ); + commands[ to.modelElement ]._doExecute(); - expect( getData( document ) ).to.equal( `<${ to.id }>foo[]bar` ); + expect( getData( document ) ).to.equal( `<${ to.modelElement }>foo[]bar` ); } ); } } ); @@ -150,28 +159,34 @@ describe( 'HeadingCommand', () => { it( 'converts all elements where selection is applied', () => { setData( document, 'foo[bar]baz' ); - commands.paragraph._doExecute(); + commands.heading3._doExecute(); expect( getData( document ) ).to.equal( - 'foo[bar]baz' + 'foo[bar]baz' ); } ); - // it( 'resets to default value all elements with same option', () => { - // setData( document, 'foo[barbaz]' ); - // commands.heading1._doExecute(); + it( 'resets to default value all elements with same option', () => { + setData( document, 'foo[barbaz]' ); + commands.heading1._doExecute(); - // expect( getData( document ) ).to.equal( - // 'foo[barbaz]' - // ); - // } ); + expect( getData( document ) ).to.equal( + 'foo[barbaz]' + ); + } ); - function test( from, to ) { - it( `converts ${ from.id } to ${ to.id } on non-collapsed selection`, () => { - setData( document, `<${ from.id }>foo[bar<${ from.id }>baz]qux` ); - commands[ to.id ]._doExecute(); + function test( { modelElement: fromElement }, { modelElement: toElement } ) { + it( `converts ${ fromElement } to ${ toElement } on non-collapsed selection`, () => { + setData( + document, + `<${ fromElement }>foo[bar<${ fromElement }>baz]qux` + ); + + commands[ toElement ]._doExecute(); - expect( getData( document ) ).to.equal( `<${ to.id }>foo[bar<${ to.id }>baz]qux` ); + expect( getData( document ) ).to.equal( + `<${ toElement }>foo[bar<${ toElement }>baz]qux` + ); } ); } } ); diff --git a/tests/headingengine.js b/tests/headingengine.js index 4b3adfe..9f9523a 100644 --- a/tests/headingengine.js +++ b/tests/headingengine.js @@ -4,9 +4,10 @@ */ import HeadingEngine from '../src/headingengine'; +import HeadingCommand from '../src/headingcommand'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; -import HeadingCommand from '../src/headingcommand'; import Enter from '@ckeditor/ckeditor5-enter/src/enter'; import { add } from '@ckeditor/ckeditor5-utils/src/translation-service'; import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; @@ -55,10 +56,10 @@ describe( 'HeadingEngine', () => { } ); it( 'should register #commands', () => { - expect( editor.commands.get( 'headingParagraph' ) ).to.be.instanceOf( HeadingCommand ); - expect( editor.commands.get( 'headingHeading1' ) ).to.be.instanceOf( HeadingCommand ); - expect( editor.commands.get( 'headingHeading2' ) ).to.be.instanceOf( HeadingCommand ); - expect( editor.commands.get( 'headingHeading3' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'paragraph' ) ).to.be.instanceOf( ParagraphCommand ); + expect( editor.commands.get( 'heading1' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'heading2' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'heading3' ) ).to.be.instanceOf( HeadingCommand ); } ); it( 'should convert heading1', () => { @@ -112,18 +113,18 @@ describe( 'HeadingEngine', () => { describe( 'default value', () => { it( 'should be set', () => { expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [ - { id: 'paragraph', element: 'p', label: 'Paragraph' }, - { id: 'heading1', element: 'h2', label: 'Heading 1' }, - { id: 'heading2', element: 'h3', label: 'Heading 2' }, - { id: 'heading3', element: 'h4', label: 'Heading 3' } + { modelElement: 'paragraph' }, + { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' }, + { modelElement: 'heading3', viewElement: 'h4', title: 'Heading 3' } ] ); } ); } ); it( 'should customize options', () => { const options = [ - { id: 'paragraph', element: 'p', label: 'Paragraph' }, - { id: 'h4', element: 'h4', label: 'H4' } + { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, + { modelElement: 'h4', viewElement: 'h4', title: 'H4' } ]; return VirtualTestEditor.create( { From 611dcf3614ab32a76490b04d89d3bbc528f67c01 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 6 Mar 2017 16:54:33 +0100 Subject: [PATCH 3/6] Removed obsolote code from HeadingCommand tests. Docs refactoring. --- src/headingcommand.js | 6 +++--- src/headingengine.js | 8 +++++--- tests/headingcommand.js | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/headingcommand.js b/src/headingcommand.js index d8311a9..68b1793 100644 --- a/src/headingcommand.js +++ b/src/headingcommand.js @@ -41,7 +41,7 @@ export default class HeadingCommand extends Command { /** * Unique identifier of the command, also element's name in the model. - * See {@link module:heading/headingcommand~HeadingOption#modelElement}. + * See {@link module:heading/headingcommand~HeadingOption}. * * @readonly * @member {String} #modelElement @@ -49,7 +49,7 @@ export default class HeadingCommand extends Command { /** * Element this command creates in the view. - * See {@link module:heading/headingcommand~HeadingOption#viewElement}. + * See {@link module:heading/headingcommand~HeadingOption}. * * @readonly * @member {String} #viewElement @@ -57,7 +57,7 @@ export default class HeadingCommand extends Command { /** * User-readable title of the command. - * See {@link module:heading/headingcommand~HeadingOption#title}. + * See {@link module:heading/headingcommand~HeadingOption}. * * @readonly * @member {String} #title diff --git a/src/headingengine.js b/src/headingengine.js index a3af511..ae4f35e 100644 --- a/src/headingengine.js +++ b/src/headingengine.js @@ -37,6 +37,8 @@ export default class HeadingEngine extends Plugin { */ this.commands = new Collection( { idProperty: 'modelElement' } ); + // TODO: This needs proper documentation, i.e. why paragraph entry does not need + // more properties (https://github.com/ckeditor/ckeditor5/issues/403). editor.config.define( 'heading', { options: [ { modelElement: 'paragraph' }, @@ -116,10 +118,10 @@ export default class HeadingEngine extends Plugin { /** * Returns heading options as defined in `config.heading.options` but processed to consider - * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption#title} + * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption} * in the correct language. * - * Note: The reason behind this method is that there's no way to use {@link utils/locale~Locale#t} + * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t} * when the user config is defined because the editor does not exist yet. * * @private @@ -141,7 +143,7 @@ export default class HeadingEngine extends Plugin { /** * Cached localized version of `config.heading.options` generated by - * {@link module:heading/headingengine~HeadingEngine#_localizedOptions}. + * {@link module:heading/headingengine~HeadingEngine#_getLocalizedOptions}. * * @private * @readonly diff --git a/tests/headingcommand.js b/tests/headingcommand.js index c230765..aea5f17 100644 --- a/tests/headingcommand.js +++ b/tests/headingcommand.js @@ -124,7 +124,7 @@ describe( 'HeadingCommand', () => { const command = commands.heading1; setData( document, 'foo[]bar' ); - command._doExecute( { modelElement: 'heading1' } ); + command._doExecute(); expect( getData( document ) ).to.equal( 'foo[]bar' ); } ); From cb71790ca606d3867a29714067c1b88845716cdf Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 8 Mar 2017 11:02:20 +0100 Subject: [PATCH 4/6] HeadingCommand passes selection to the ParagraphCommand instead of element. Refactoring in Heading after removal of ParagraphCommand#title. --- src/heading.js | 46 ++++++++++++++++++++++++++++++++----------- src/headingcommand.js | 12 ++++++++--- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/heading.js b/src/heading.js index 8a33f51..852a528 100644 --- a/src/heading.js +++ b/src/heading.js @@ -34,25 +34,22 @@ export default class Heading extends Plugin { */ init() { const editor = this.editor; + const t = editor.t; const headingEngine = editor.plugins.get( HeadingEngine ); const commands = headingEngine.commands; const dropdownItems = new Collection(); let defaultCommand; for ( let command of commands ) { - let modelElement, title; + // Add the option to the collection. + dropdownItems.add( new Model( { + modelElement: getCommandModelElement( command ), + label: getCommandTitle( command, t ) + } ) ); - if ( command instanceof HeadingCommand ) { - modelElement = command.modelElement; - } else { - modelElement = 'paragraph'; + if ( !( command instanceof HeadingCommand ) ) { defaultCommand = command; } - - title = command.title; - - // Add the option to the collection. - dropdownItems.add( new Model( { modelElement, label: title } ) ); } // Create dropdown model. @@ -76,7 +73,7 @@ export default class Heading extends Plugin { const index = areActive.findIndex( value => value ); // If none of the commands is active, display the first one. - return index > -1 ? commands.get( index ).title : defaultCommand.title; + return getCommandTitle( index > -1 ? commands.get( index ) : defaultCommand, t ); } ); @@ -95,6 +92,33 @@ export default class Heading extends Plugin { } } +// Returns an array of binding components for +// {@link module:utils/observablemixin~Observable#bind} from a set of iterable +// commands. +// +// @private +// @param {Iterable.} commands +// @param {String} attribute +// @returns {Array.} function getCommandsBindingTargets( commands, attribute ) { return Array.prototype.concat( ...commands.map( c => [ c, attribute ] ) ); } + +// Returns the `modelElement` string for given command. +// +// @private +// @param {module:core/command/command~Command} command +// @returns {String} +function getCommandModelElement( command ) { + return command instanceof HeadingCommand ? command.modelElement : 'paragraph'; +} + +// Returns the `title` string for given command. +// +// @private +// @param {module:core/command/command~Command} command +// @param {module:utils/locale~Locale#t} t +// @returns {String} +function getCommandTitle( command, t ) { + return command instanceof HeadingCommand ? command.title : t( 'Paragraph' ); +} diff --git a/src/headingcommand.js b/src/headingcommand.js index 68b1793..196df5d 100644 --- a/src/headingcommand.js +++ b/src/headingcommand.js @@ -7,7 +7,9 @@ * @module heading/headingcommand */ +import Range from '@ckeditor/ckeditor5-engine/src/model/range'; import Command from '@ckeditor/ckeditor5-core/src/command/command'; +import Selection from '@ckeditor/ckeditor5-engine/src/model/selection'; /** * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings. @@ -89,9 +91,13 @@ export default class HeadingCommand extends Command { // When removing applied option. if ( shouldRemove ) { if ( element.name === this.modelElement ) { - // Apply paragraph to the single element only instead of working - // on the entire selection. Share the batch with the paragraph command. - editor.execute( 'paragraph', { element, batch } ); + // Apply paragraph to the selection withing that particular element only instead + // of working on the entire document selection. + const selection = new Selection(); + selection.addRange( Range.createIn( element ) ); + + // Share the batch with the paragraph command. + editor.execute( 'paragraph', { selection, batch } ); } } // When applying new option. From 7b2c1c8f0f82a74557e8b38c3e64d34b22663c8b Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 8 Mar 2017 15:56:03 +0100 Subject: [PATCH 5/6] Removed HeadingEngine#commands. Simplified config.heading.options localization. --- src/heading.js | 80 ++++++++++++++++++++++++++---------------- src/headingengine.js | 65 ++-------------------------------- tests/heading.js | 25 ++++++++----- tests/headingengine.js | 7 ++-- 4 files changed, 70 insertions(+), 107 deletions(-) diff --git a/src/heading.js b/src/heading.js index 852a528..b7b9e42 100644 --- a/src/heading.js +++ b/src/heading.js @@ -8,7 +8,6 @@ */ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import HeadingCommand from './headingcommand'; import HeadingEngine from './headingengine'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Model from '@ckeditor/ckeditor5-ui/src/model'; @@ -34,21 +33,22 @@ export default class Heading extends Plugin { */ init() { const editor = this.editor; - const t = editor.t; - const headingEngine = editor.plugins.get( HeadingEngine ); - const commands = headingEngine.commands; const dropdownItems = new Collection(); - let defaultCommand; + const options = this._getLocalizedOptions(); + const commands = []; + let defaultOption; - for ( let command of commands ) { + for ( let option of options ) { // Add the option to the collection. dropdownItems.add( new Model( { - modelElement: getCommandModelElement( command ), - label: getCommandTitle( command, t ) + modelElement: option.modelElement, + label: option.title } ) ); - if ( !( command instanceof HeadingCommand ) ) { - defaultCommand = command; + commands.push( editor.commands.get( option.modelElement ) ); + + if ( !defaultOption && option.modelElement == 'paragraph' ) { + defaultOption = option; } } @@ -73,7 +73,7 @@ export default class Heading extends Plugin { const index = areActive.findIndex( value => value ); // If none of the commands is active, display the first one. - return getCommandTitle( index > -1 ? commands.get( index ) : defaultCommand, t ); + return ( options[ index ] || defaultOption ).title; } ); @@ -90,6 +90,45 @@ export default class Heading extends Plugin { return dropdown; } ); } + + /** + * Returns heading options as defined in `config.heading.options` but processed to consider + * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption} + * in the correct language. + * + * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t} + * when the user config is defined because the editor does not exist yet. + * + * @private + * @returns {Array.}. + */ + _getLocalizedOptions() { + const editor = this.editor; + const t = editor.t; + const localizedTitles = { + Paragraph: t( 'Paragraph' ), + 'Heading 1': t( 'Heading 1' ), + 'Heading 2': t( 'Heading 2' ), + 'Heading 3': t( 'Heading 3' ) + }; + + return editor.config.get( 'heading.options' ).map( option => { + let title; + + if ( option.modelElement == 'paragraph' ) { + title = localizedTitles.Paragraph; + } else { + title = localizedTitles[ option.title ]; + } + + if ( title && title != option.title ) { + // Clone the option to avoid altering the original `config.heading.options`. + option = Object.assign( {}, option, { title } ); + } + + return option; + } ); + } } // Returns an array of binding components for @@ -103,22 +142,3 @@ export default class Heading extends Plugin { function getCommandsBindingTargets( commands, attribute ) { return Array.prototype.concat( ...commands.map( c => [ c, attribute ] ) ); } - -// Returns the `modelElement` string for given command. -// -// @private -// @param {module:core/command/command~Command} command -// @returns {String} -function getCommandModelElement( command ) { - return command instanceof HeadingCommand ? command.modelElement : 'paragraph'; -} - -// Returns the `title` string for given command. -// -// @private -// @param {module:core/command/command~Command} command -// @param {module:utils/locale~Locale#t} t -// @returns {String} -function getCommandTitle( command, t ) { - return command instanceof HeadingCommand ? command.title : t( 'Paragraph' ); -} diff --git a/src/headingengine.js b/src/headingengine.js index ae4f35e..708e4fa 100644 --- a/src/headingengine.js +++ b/src/headingengine.js @@ -8,7 +8,6 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter'; import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; @@ -29,14 +28,6 @@ export default class HeadingEngine extends Plugin { constructor( editor ) { super( editor ); - /** - * A collection of heading commands associated with heading engine. - * - * @readonly - * @member {module:utils/collection~Collection.} - */ - this.commands = new Collection( { idProperty: 'modelElement' } ); - // TODO: This needs proper documentation, i.e. why paragraph entry does not need // more properties (https://github.com/ckeditor/ckeditor5/issues/403). editor.config.define( 'heading', { @@ -63,7 +54,7 @@ export default class HeadingEngine extends Plugin { const editor = this.editor; const data = editor.data; const editing = editor.editing; - const options = this._getLocalizedOptions(); + const options = editor.config.get( 'heading.options' ); let command; for ( let option of options ) { @@ -85,11 +76,7 @@ export default class HeadingEngine extends Plugin { // Register the heading command for this option. command = new HeadingCommand( editor, option ); editor.commands.set( command.modelElement, command ); - } else { - command = editor.commands.get( defaultModelElement ); } - - this.commands.add( command ); } } @@ -101,7 +88,7 @@ export default class HeadingEngine extends Plugin { // Enter at the end of a heading element should create a paragraph. const editor = this.editor; const enterCommand = editor.commands.get( 'enter' ); - const options = this._getLocalizedOptions(); + const options = editor.config.get( 'heading.options' ); if ( enterCommand ) { this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => { @@ -115,52 +102,4 @@ export default class HeadingEngine extends Plugin { } ); } } - - /** - * Returns heading options as defined in `config.heading.options` but processed to consider - * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption} - * in the correct language. - * - * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t} - * when the user config is defined because the editor does not exist yet. - * - * @private - * @returns {Array.}. - */ - _getLocalizedOptions() { - if ( this._cachedLocalizedOptions ) { - return this._cachedLocalizedOptions; - } - - const editor = this.editor; - const t = editor.t; - const localizedLabels = { - Paragraph: t( 'Paragraph' ), - 'Heading 1': t( 'Heading 1' ), - 'Heading 2': t( 'Heading 2' ), - 'Heading 3': t( 'Heading 3' ) - }; - - /** - * Cached localized version of `config.heading.options` generated by - * {@link module:heading/headingengine~HeadingEngine#_getLocalizedOptions}. - * - * @private - * @readonly - * @member {Array.} #_cachedLocalizedOptions - */ - this._cachedLocalizedOptions = editor.config.get( 'heading.options' ) - .map( option => { - if ( localizedLabels[ option.title ] ) { - // Clone the option to avoid altering the original `config.heading.options`. - option = Object.assign( {}, option, { - title: localizedLabels[ option.title ] - } ); - } - - return option; - } ); - - return this._cachedLocalizedOptions; - } } diff --git a/tests/heading.js b/tests/heading.js index 724de28..802ff73 100644 --- a/tests/heading.js +++ b/tests/heading.js @@ -84,28 +84,32 @@ describe( 'Heading', () => { let commands; beforeEach( () => { - commands = editor.plugins.get( HeadingEngine ).commands; + commands = {}; + + editor.config.get( 'heading.options' ).forEach( ( { modelElement } ) => { + commands[ modelElement ] = editor.commands.get( modelElement ); + } ); } ); it( 'isEnabled', () => { - for ( let command of commands ) { - command.isEnabled = false; + for ( let name in commands ) { + commands[ name ].isEnabled = false; } expect( dropdown.buttonView.isEnabled ).to.be.false; - commands.get( 'heading2' ).isEnabled = true; + commands.heading2.isEnabled = true; expect( dropdown.buttonView.isEnabled ).to.be.true; } ); it( 'label', () => { - for ( let command of commands ) { - command.value = false; + for ( let name in commands ) { + commands[ name ].value = false; } expect( dropdown.buttonView.label ).to.equal( 'Paragraph' ); - commands.get( 'heading2' ).value = true; + commands.heading2.value = true; expect( dropdown.buttonView.label ).to.equal( 'Heading 2' ); } ); } ); @@ -131,7 +135,10 @@ describe( 'Heading', () => { .then( newEditor => { editor = newEditor; dropdown = editor.ui.componentFactory.create( 'headings' ); - commands = editor.plugins.get( HeadingEngine ).commands; + commands = {}; + editor.config.get( 'heading.options' ).forEach( ( { modelElement } ) => { + commands[ modelElement ] = editor.commands.get( modelElement ); + } ); } ); } ); @@ -147,7 +154,7 @@ describe( 'Heading', () => { const buttonView = dropdown.buttonView; expect( buttonView.label ).to.equal( 'Akapit' ); - commands.get( 'heading1' ).value = true; + commands.heading1.value = true; expect( buttonView.label ).to.equal( 'Nagłówek 1' ); } ); diff --git a/tests/headingengine.js b/tests/headingengine.js index 9f9523a..a307e37 100644 --- a/tests/headingengine.js +++ b/tests/headingengine.js @@ -136,11 +136,8 @@ describe( 'HeadingEngine', () => { .then( editor => { document = editor.document; - const commands = editor.plugins.get( HeadingEngine ).commands; - - expect( commands ).to.have.length( 2 ); - expect( commands.get( 0 ).id ).to.equal( options[ 0 ].id ); - expect( commands.get( 1 ).id ).to.equal( options[ 1 ].id ); + expect( editor.commands.get( 'h4' ) ).to.be.instanceOf( HeadingCommand ); + expect( editor.commands.get( 'paragraph' ) ).to.be.instanceOf( ParagraphCommand ); expect( document.schema.hasItem( 'paragraph' ) ).to.be.true; expect( document.schema.hasItem( 'h4' ) ).to.be.true; From 65ac56c5da1b301fcd4950dba0ec933b2fcc60be Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 9 Mar 2017 12:00:27 +0100 Subject: [PATCH 6/6] Code refac. Deobfuscated some pieces of code. Allowed custom title for paragraph option. Enhanced tests. --- src/heading.js | 14 ++----- src/headingcommand.js | 21 ++++------- src/headingengine.js | 10 ++--- tests/heading.js | 84 ++++++++++++++++++++++++++++------------- tests/headingcommand.js | 2 +- tests/headingengine.js | 12 +----- 6 files changed, 75 insertions(+), 68 deletions(-) diff --git a/src/heading.js b/src/heading.js index b7b9e42..0712c09 100644 --- a/src/heading.js +++ b/src/heading.js @@ -41,7 +41,7 @@ export default class Heading extends Plugin { for ( let option of options ) { // Add the option to the collection. dropdownItems.add( new Model( { - modelElement: option.modelElement, + commandName: option.modelElement, label: option.title } ) ); @@ -82,8 +82,8 @@ export default class Heading extends Plugin { const dropdown = createListDropdown( dropdownModel, locale ); // Execute command when an item from the dropdown is selected. - this.listenTo( dropdown, 'execute', ( { source: { modelElement } } ) => { - editor.execute( modelElement ); + this.listenTo( dropdown, 'execute', ( evt ) => { + editor.execute( evt.source.commandName ); editor.editing.view.focus(); } ); @@ -113,13 +113,7 @@ export default class Heading extends Plugin { }; return editor.config.get( 'heading.options' ).map( option => { - let title; - - if ( option.modelElement == 'paragraph' ) { - title = localizedTitles.Paragraph; - } else { - title = localizedTitles[ option.title ]; - } + const title = localizedTitles[ option.title ]; if ( title && title != option.title ) { // Clone the option to avoid altering the original `config.heading.options`. diff --git a/src/headingcommand.js b/src/headingcommand.js index 196df5d..846a01c 100644 --- a/src/headingcommand.js +++ b/src/headingcommand.js @@ -77,9 +77,6 @@ export default class HeadingCommand extends Command { _doExecute( options = {} ) { const editor = this.editor; const document = editor.document; - const selection = document.selection; - const ranges = [ ...selection.getRanges() ]; - const isSelectionBackward = selection.isBackward; // If current option is same as new option - toggle already applied option back to default one. const shouldRemove = this.value; @@ -87,28 +84,24 @@ export default class HeadingCommand extends Command { document.enqueueChanges( () => { const batch = options.batch || document.batch(); - for ( let element of document.selection.getSelectedBlocks() ) { + for ( let block of document.selection.getSelectedBlocks() ) { // When removing applied option. if ( shouldRemove ) { - if ( element.name === this.modelElement ) { - // Apply paragraph to the selection withing that particular element only instead + if ( block.is( this.modelElement ) ) { + // Apply paragraph to the selection withing that particular block only instead // of working on the entire document selection. const selection = new Selection(); - selection.addRange( Range.createIn( element ) ); + selection.addRange( Range.createIn( block ) ); // Share the batch with the paragraph command. editor.execute( 'paragraph', { selection, batch } ); } } // When applying new option. - else { - batch.rename( element, this.modelElement ); + else if ( !block.is( this.modelElement ) ) { + batch.rename( block, this.modelElement ); } } - - // If range's selection start/end is placed directly in renamed block - we need to restore it's position - // after renaming, because renaming puts new element there. - selection.setRanges( ranges, isSelectionBackward ); } ); } @@ -121,7 +114,7 @@ export default class HeadingCommand extends Command { const block = this.editor.document.selection.getSelectedBlocks().next().value; if ( block ) { - this.value = this.modelElement == block.name; + this.value = block.is( this.modelElement ); } } } diff --git a/src/headingengine.js b/src/headingengine.js index 708e4fa..e7dcd7b 100644 --- a/src/headingengine.js +++ b/src/headingengine.js @@ -32,7 +32,7 @@ export default class HeadingEngine extends Plugin { // more properties (https://github.com/ckeditor/ckeditor5/issues/403). editor.config.define( 'heading', { options: [ - { modelElement: 'paragraph' }, + { modelElement: 'paragraph', title: 'Paragraph' }, { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' }, { modelElement: 'heading3', viewElement: 'h4', title: 'Heading 3' } @@ -55,7 +55,6 @@ export default class HeadingEngine extends Plugin { const data = editor.data; const editing = editor.editing; const options = editor.config.get( 'heading.options' ); - let command; for ( let option of options ) { // Skip paragraph - it is defined in required Paragraph feature. @@ -74,8 +73,7 @@ export default class HeadingEngine extends Plugin { .toElement( option.modelElement ); // Register the heading command for this option. - command = new HeadingCommand( editor, option ); - editor.commands.set( command.modelElement, command ); + editor.commands.set( option.modelElement, new HeadingCommand( editor, option ) ); } } } @@ -94,9 +92,9 @@ export default class HeadingEngine extends Plugin { this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => { const positionParent = editor.document.selection.getFirstPosition().parent; const batch = data.batch; - const isHeading = options.some( option => option.modelElement == positionParent.name ); + const isHeading = options.some( option => positionParent.is( option.modelElement ) ); - if ( isHeading && positionParent.name != defaultModelElement && positionParent.childCount === 0 ) { + if ( isHeading && !positionParent.is( defaultModelElement ) && positionParent.childCount === 0 ) { batch.rename( positionParent, defaultModelElement ); } } ); diff --git a/tests/heading.js b/tests/heading.js index 802ff73..c0ae1c8 100644 --- a/tests/heading.js +++ b/tests/heading.js @@ -63,7 +63,7 @@ describe( 'Heading', () => { const executeSpy = testUtils.sinon.spy( editor, 'execute' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); - dropdown.modelElement = 'paragraph'; + dropdown.commandName = 'paragraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( executeSpy ); @@ -74,7 +74,7 @@ describe( 'Heading', () => { const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' ); const dropdown = editor.ui.componentFactory.create( 'headings' ); - dropdown.modelElement = 'paragraph'; + dropdown.commandName = 'paragraph'; dropdown.fire( 'execute' ); sinon.assert.calledOnce( focusSpy ); @@ -118,35 +118,18 @@ describe( 'Heading', () => { let commands; beforeEach( () => { - const editorElement = document.createElement( 'div' ); - - return ClassicTestEditor.create( editorElement, { - plugins: [ Heading ], - toolbar: [ 'heading' ], - lang: 'pl', - heading: { - options: [ - { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, - { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, - { modelElement: 'heading2', viewElement: 'h3', title: 'Not automatically localized' } - ] - } - } ) - .then( newEditor => { - editor = newEditor; - dropdown = editor.ui.componentFactory.create( 'headings' ); - commands = {}; - editor.config.get( 'heading.options' ).forEach( ( { modelElement } ) => { - commands[ modelElement ] = editor.commands.get( modelElement ); - } ); - } ); + return localizedEditor( [ + { modelElement: 'paragraph', title: 'Paragraph' }, + { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, + { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' } + ] ); } ); it( 'does not alter the original config', () => { expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [ - { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, + { modelElement: 'paragraph', title: 'Paragraph' }, { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, - { modelElement: 'heading2', viewElement: 'h3', title: 'Not automatically localized' } + { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' } ] ); } ); @@ -164,9 +147,56 @@ describe( 'Heading', () => { expect( listView.items.map( item => item.label ) ).to.deep.equal( [ 'Akapit', 'Nagłówek 1', - 'Not automatically localized' + 'Nagłówek 2' ] ); } ); + + it( 'allows custom titles', () => { + return localizedEditor( [ + { modelElement: 'paragraph', title: 'Custom paragraph title' }, + { modelElement: 'heading1', title: 'Custom heading1 title' } + ] ).then( () => { + const listView = dropdown.listView; + + expect( listView.items.map( item => item.label ) ).to.deep.equal( [ + 'Custom paragraph title', + 'Custom heading1 title', + ] ); + } ); + } ); + + it( 'translates default using the the locale', () => { + return localizedEditor( [ + { modelElement: 'paragraph', title: 'Paragraph' } + ] ).then( () => { + const listView = dropdown.listView; + + expect( listView.items.map( item => item.label ) ).to.deep.equal( [ + 'Akapit' + ] ); + } ); + } ); + + function localizedEditor( options ) { + const editorElement = document.createElement( 'div' ); + + return ClassicTestEditor.create( editorElement, { + plugins: [ Heading ], + toolbar: [ 'heading' ], + lang: 'pl', + heading: { + options: options + } + } ) + .then( newEditor => { + editor = newEditor; + dropdown = editor.ui.componentFactory.create( 'headings' ); + commands = {}; + editor.config.get( 'heading.options' ).forEach( ( { modelElement } ) => { + commands[ modelElement ] = editor.commands.get( modelElement ); + } ); + } ); + } } ); } ); } ); diff --git a/tests/headingcommand.js b/tests/headingcommand.js index aea5f17..26dcdb0 100644 --- a/tests/headingcommand.js +++ b/tests/headingcommand.js @@ -158,7 +158,7 @@ describe( 'HeadingCommand', () => { } it( 'converts all elements where selection is applied', () => { - setData( document, 'foo[bar]baz' ); + setData( document, 'foo[bar]baz' ); commands.heading3._doExecute(); expect( getData( document ) ).to.equal( diff --git a/tests/headingengine.js b/tests/headingengine.js index a307e37..fc4647e 100644 --- a/tests/headingengine.js +++ b/tests/headingengine.js @@ -9,16 +9,8 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import Enter from '@ckeditor/ckeditor5-enter/src/enter'; -import { add } from '@ckeditor/ckeditor5-utils/src/translation-service'; import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; -add( 'pl', { - 'Paragraph': 'Akapit', - 'Heading 1': 'Nagłówek 1', - 'Heading 2': 'Nagłówek 2', - 'Heading 3': 'Nagłówek 3', -} ); - describe( 'HeadingEngine', () => { let editor, document; @@ -113,7 +105,7 @@ describe( 'HeadingEngine', () => { describe( 'default value', () => { it( 'should be set', () => { expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [ - { modelElement: 'paragraph' }, + { modelElement: 'paragraph', title: 'Paragraph' }, { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1' }, { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2' }, { modelElement: 'heading3', viewElement: 'h4', title: 'Heading 3' } @@ -123,7 +115,7 @@ describe( 'HeadingEngine', () => { it( 'should customize options', () => { const options = [ - { modelElement: 'paragraph', viewElement: 'p', title: 'Paragraph' }, + { modelElement: 'paragraph', title: 'Paragraph' }, { modelElement: 'h4', viewElement: 'h4', title: 'H4' } ];