8
0
Quellcode durchsuchen

Merge pull request #15 from ckeditor/t/14

Feature: Implemented `ParagraphCommand`, previously part of the `HeadingCommand`. Closes #14.
Piotrek Koszuliński vor 9 Jahren
Ursprung
Commit
319515e3f3

+ 3 - 0
packages/ckeditor5-paragraph/src/paragraph.js

@@ -7,6 +7,7 @@
  * @module paragraph/paragraph
  */
 
+import ParagraphCommand from './paragraphcommand';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
@@ -65,6 +66,8 @@ export default class Paragraph extends Plugin {
 		data.viewToModel.on( 'element', ( evt, data, consumable, conversionApi ) => {
 			autoparagraphParagraphLikeElements( doc, evt, data, consumable, conversionApi );
 		}, { priority: 'low' } );
+
+		editor.commands.set( 'paragraph', new ParagraphCommand( editor ) );
 	}
 }
 

+ 78 - 0
packages/ckeditor5-paragraph/src/paragraphcommand.js

@@ -0,0 +1,78 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module paragraph/paragraphcommand
+ */
+
+import Command from '@ckeditor/ckeditor5-core/src/command/command';
+
+/**
+ * The paragraph command.
+ *
+ * @extends module:core/command/command~Command
+ */
+export default class ParagraphCommand extends Command {
+	/**
+	 * Creates an instance of the command.
+	 *
+	 * @param {module:core/editor/editor~Editor} editor Editor instance.
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * Value of the command, indicating whether it is applied in the context
+		 * of current {@link module:engine/model/document~Document#selection selection}.
+		 *
+		 * @readonly
+		 * @observable
+		 * @member {Boolean}
+		 */
+		this.set( 'value', false );
+
+		// Update current value each time changes are done on document.
+		this.listenTo( editor.document, 'changesDone', () => this._updateValue() );
+	}
+
+	/**
+	 * Executes the command. All the {@link module:engine/model/schema~Schema#blocks blocks} in the selection
+	 * will be turned to paragraphs.
+	 *
+	 * @protected
+	 * @param {Object} [options] Options for executed command.
+	 * @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.
+	 * @param {module:engine/model/selection~Selection} [options.selection] Selection the command should be applied to.
+	 * By default, if not provided, the command is applied to {@link module:engine/model/document~Document#selection}.
+	 */
+	_doExecute( options = {} ) {
+		const document = this.editor.document;
+
+		document.enqueueChanges( () => {
+			const batch = options.batch || document.batch();
+			const blocks = ( options.selection || document.selection ).getSelectedBlocks();
+
+			for ( let block of blocks ) {
+				if ( !block.is( 'paragraph' ) ) {
+					batch.rename( block, 'paragraph' );
+				}
+			}
+		} );
+	}
+
+	/**
+	 * Updates command's {@link #value value} based on current selection.
+	 *
+	 * @private
+	 */
+	_updateValue() {
+		const block = this.editor.document.selection.getSelectedBlocks().next().value;
+
+		if ( block ) {
+			this.value = block.is( 'paragraph' );
+		}
+	}
+}

+ 7 - 0
packages/ckeditor5-paragraph/tests/paragraph.js

@@ -4,6 +4,7 @@
  */
 
 import Paragraph from '../src/paragraph';
+import ParagraphCommand from '../src/paragraphcommand';
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import {
 	getData as getModelData,
@@ -337,4 +338,10 @@ describe( 'Paragraph feature', () => {
 			expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo</paragraph>' );
 		} );
 	} );
+
+	describe( 'command', () => {
+		it( 'should be set in the editor', () => {
+			expect( editor.commands.get( 'paragraph' ) ).to.be.instanceof( ParagraphCommand );
+		} );
+	} );
 } );

+ 150 - 0
packages/ckeditor5-paragraph/tests/paragraphcommand.js

@@ -0,0 +1,150 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+import ParagraphCommand from '../src/paragraphcommand';
+import Selection from '@ckeditor/ckeditor5-engine/src/model/selection';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
+import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+describe( 'ParagraphCommand', () => {
+	let editor, document, command, root, schema;
+
+	beforeEach( () => {
+		return ModelTestEditor.create().then( ( newEditor ) => {
+			editor = newEditor;
+			document = editor.document;
+			schema = document.schema;
+			command = new ParagraphCommand( editor );
+			root = document.getRoot();
+
+			editor.commands.set( 'paragraph', command );
+			schema.registerItem( 'paragraph', '$block' );
+			schema.registerItem( 'heading1', '$block' );
+		} );
+	} );
+
+	afterEach( () => {
+		command.destroy();
+	} );
+
+	describe( 'value', () => {
+		it( 'has default value', () => {
+			setData( document, '' );
+
+			expect( command.value ).to.be.false;
+		} );
+
+		it( 'responds to changes in selection (collapsed selection)', () => {
+			setData( document, '<heading1>foo[]bar</heading1>' );
+			expect( command.value ).to.be.false;
+
+			setData( document, '<paragraph>foo[]bar</paragraph>' );
+			expect( command.value ).to.be.true;
+		} );
+
+		it( 'responds to changes in selection (non–collapsed selection)', () => {
+			setData( document, '<heading1>[foo]</heading1><paragraph>bar</paragraph>' );
+			expect( command.value ).to.be.false;
+
+			setData( document, '<heading1>[foo</heading1><paragraph>bar]</paragraph>' );
+			expect( command.value ).to.be.false;
+
+			setData( document, '<heading1>foo</heading1>[<paragraph>bar]</paragraph>' );
+			expect( command.value ).to.be.true;
+
+			setData( document, '<heading1>foo</heading1><paragraph>[bar]</paragraph>' );
+			expect( command.value ).to.be.true;
+
+			setData( document, '<paragraph>[bar</paragraph><heading1>foo]</heading1>' );
+			expect( command.value ).to.be.true;
+		} );
+	} );
+
+	describe( '_doExecute', () => {
+		it( 'should update value after execution', () => {
+			setData( document, '<heading1>[]</heading1>' );
+			command._doExecute();
+
+			expect( getData( document ) ).to.equal( '<paragraph>[]</paragraph>' );
+			expect( command.value ).to.be.true;
+		} );
+
+		it( 'should not rename blocks which already are pargraphs', () => {
+			const batch = editor.document.batch();
+
+			setData( document, '<paragraph>foo[</paragraph><heading1>bar]</heading1>' );
+			expect( batch.deltas.length ).to.equal( 0 );
+
+			command._doExecute( { batch } );
+			expect( batch.deltas.length ).to.equal( 1 );
+		} );
+
+		describe( 'custom options', () => {
+			it( 'should use provided batch', () => {
+				const batch = editor.document.batch();
+
+				setData( document, '<heading1>foo[]bar</heading1>' );
+				expect( batch.deltas.length ).to.equal( 0 );
+
+				command._doExecute( { batch } );
+				expect( batch.deltas.length ).to.be.above( 0 );
+			} );
+
+			it( 'should use provided selection', () => {
+				setData( document, '<heading1>foo[]bar</heading1><heading1>baz</heading1><heading1>qux</heading1>' );
+
+				const secondTolastHeading = root.getChild( 1 );
+				const lastHeading = root.getChild( 2 );
+				const selection = new Selection();
+				selection.addRange( Range.createFromParentsAndOffsets( secondTolastHeading, 0, lastHeading, 0 ) );
+
+				command._doExecute( { selection } );
+				expect( getData( document ) ).to.equal( '<heading1>foo[]bar</heading1><paragraph>baz</paragraph><paragraph>qux</paragraph>' );
+			} );
+		} );
+
+		describe( 'collapsed selection', () => {
+			it( 'does nothing when executed with already applied', () => {
+				setData( document, '<paragraph>foo[]bar</paragraph>' );
+				command._doExecute();
+
+				expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
+			} );
+
+			it( 'converts topmost blocks', () => {
+				schema.registerItem( 'inlineImage', '$inline' );
+				schema.allow( { name: '$text', inside: 'inlineImage' } );
+
+				setData( document, '<heading1><inlineImage>foo[]</inlineImage>bar</heading1>' );
+				command._doExecute();
+
+				expect( getData( document ) ).to.equal( '<paragraph><inlineImage>foo[]</inlineImage>bar</paragraph>' );
+			} );
+		} );
+
+		describe( 'non-collapsed selection', () => {
+			it( 'converts all elements where selection is applied', () => {
+				schema.registerItem( 'heading2', '$block' );
+
+				setData( document, '<heading1>foo[</heading1><heading2>bar</heading2><heading2>]baz</heading2>' );
+
+				command._doExecute();
+				expect( getData( document ) ).to.equal(
+					'<paragraph>foo[</paragraph><paragraph>bar</paragraph><paragraph>]baz</paragraph>'
+				);
+			} );
+
+			it( 'converts all elements even if already anchored in paragraph', () => {
+				schema.registerItem( 'heading2', '$block' );
+
+				setData( document, '<paragraph>foo[</paragraph><heading2>bar]</heading2>' );
+
+				command._doExecute();
+				expect( getData( document ) ).to.equal( '<paragraph>foo[</paragraph><paragraph>bar]</paragraph>' );
+			} );
+		} );
+	} );
+} );