Explorar el Código

Introduced command for toggling checked state on todo list.

Oskar Wróbel hace 6 años
padre
commit
3017b3ffa0

+ 81 - 0
packages/ckeditor5-list/src/todolistcheckedcommand.js

@@ -0,0 +1,81 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module list/todolistcheckedcommand
+ */
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+const attributeKey = 'todoListChecked';
+
+/**
+ * @extends module:core/command~Command
+ */
+export default class TodoListCheckedCommand extends Command {
+	/**
+	 * @param {module:core/editor/editor~Editor} editor
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		this._selectedElements = [];
+
+		/**
+		 * Flag indicating whether the command is active. The command is active when the
+		 * {@link module:engine/model/selection~Selection#hasAttribute selection has the attribute} which means that:
+		 *
+		 * * If the selection is not empty – That the attribute is set on the first node in the selection that allows this attribute.
+		 * * If the selection is empty – That the selection has the attribute itself (which means that newly typed
+		 * text will have this attribute, too).
+		 *
+		 * @observable
+		 * @readonly
+		 * @member {Boolean} #value
+		 */
+	}
+
+	/**
+	 * Updates the command's {@link #value} and {@link #isEnabled} based on the current selection.
+	 */
+	refresh() {
+		this._selectedElements = this._getSelectedItems();
+		this.value = this._selectedElements.every( element => !!element.getAttribute( 'todoListChecked' ) );
+		this.isEnabled = !!this._selectedElements.length;
+	}
+
+	_getSelectedItems() {
+		const model = this.editor.model;
+		const schema = model.schema;
+
+		const selectionRange = model.document.selection.getFirstRange();
+		const startElement = selectionRange.start.parent;
+		const elements = [];
+
+		if ( schema.checkAttribute( startElement, attributeKey ) ) {
+			elements.push( startElement );
+		}
+
+		for ( const item of selectionRange.getItems() ) {
+			if ( schema.checkAttribute( item, attributeKey ) && !elements.includes( item ) ) {
+				elements.push( item );
+			}
+		}
+
+		return elements;
+	}
+
+	execute() {
+		this.editor.model.change( writer => {
+			for ( const element of this._selectedElements ) {
+				if ( !this.value ) {
+					writer.setAttribute( attributeKey, true, element );
+				} else {
+					writer.removeAttribute( attributeKey, element );
+				}
+			}
+		} );
+	}
+}

+ 208 - 0
packages/ckeditor5-list/tests/todolistcheckedcommand.js

@@ -0,0 +1,208 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import TodoListEditing from '../src/todolistediting';
+import TodoListCheckedCommand from '../src/todolistcheckedcommand';
+
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+describe( 'TodoListCheckedCommand', () => {
+	let editor, model, command;
+
+	beforeEach( () => {
+		return ModelTestEditor
+			.create( {
+				plugins: [ TodoListEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				command = new TodoListCheckedCommand( editor );
+			} );
+	} );
+
+	describe( 'value', () => {
+		it( 'should be false when selection is in not checked element', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">ab[]c</listItem>' );
+
+			expect( command.value ).to.equal( false );
+		} );
+
+		it( 'should be true when selection is in checked element', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo" todoListChecked="true">ab[]c</listItem>' );
+
+			expect( command.value ).to.equal( true );
+		} );
+
+		it( 'should be false when at least one selected element is not checked', () => {
+			setModelData( model,
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">ab[c</listItem>' +
+				'<paragraph>abc</paragraph>' +
+				'<listItem listIndent="0" listType="todo">abc</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">ab]c</listItem>'
+			);
+
+			expect( command.value ).to.equal( false );
+		} );
+
+		it( 'should be true when all selected elements are checked', () => {
+			setModelData( model,
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">ab[c</listItem>' +
+				'<paragraph>abc</paragraph>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">abc</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">ab]c</listItem>'
+			);
+
+			expect( command.value ).to.equal( true );
+		} );
+	} );
+
+	describe( 'isEnabled', () => {
+		it( 'should be enabled when selection is inside todo list item', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">a[b]c</listItem>' );
+
+			expect( command.isEnabled ).to.equal( true );
+		} );
+
+		it( 'should be disabled when selection is not inside todo list item', () => {
+			setModelData( model, '<paragraph>a[b]c</paragraph>' );
+
+			expect( command.isEnabled ).to.equal( false );
+		} );
+
+		it( 'should be enabled when at least one todo list item is selected', () => {
+			setModelData( model,
+				'<paragraph>a[bc</paragraph>' +
+				'<listItem listIndent="0" listType="todo">abc</listItem>' +
+				'<paragraph>ab]c</paragraph>'
+			);
+
+			expect( command.isEnabled ).to.equal( true );
+		} );
+
+		it( 'should be enabled when none todo list item is selected', () => {
+			setModelData( model,
+				'<paragraph>a[bc</paragraph>' +
+				'<paragraph>abc</paragraph>' +
+				'<paragraph>a]bc</paragraph>'
+			);
+
+			expect( command.isEnabled ).to.equal( false );
+		} );
+	} );
+
+	describe( 'execute()', () => {
+		it( 'should toggle checked state on todo list item when collapsed selection is inside this item', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">b[]ar</listItem>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">b[]ar</listItem>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo">b[]ar</listItem>'
+			);
+		} );
+
+		it( 'should toggle checked state on todo list item when non-collapsed selection is inside this item', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">b[a]r</listItem>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">b[a]r</listItem>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo">b[a]r</listItem>'
+			);
+		} );
+
+		it( 'should toggle state on multiple items', () => {
+			setModelData( model,
+				'<listItem listIndent="0" listType="todo">abc[</listItem>' +
+				'<listItem listIndent="0" listType="todo">def</listItem>' +
+				'<listItem listIndent="0" listType="todo">]ghi</listItem>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">abc[</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">def</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">]ghi</listItem>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo">abc[</listItem>' +
+				'<listItem listIndent="0" listType="todo">def</listItem>' +
+				'<listItem listIndent="0" listType="todo">]ghi</listItem>'
+			);
+		} );
+
+		it( 'should toggle state on multiple items mixed with non list item', () => {
+			setModelData( model,
+				'<paragraph>a[bc</paragraph>' +
+				'<listItem listIndent="0" listType="todo">def</listItem>' +
+				'<paragraph>ghi</paragraph>' +
+				'<listItem listIndent="0" listType="todo">jkl</listItem>' +
+				'<paragraph>mn]o</paragraph>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>a[bc</paragraph>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">def</listItem>' +
+				'<paragraph>ghi</paragraph>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">jkl</listItem>' +
+				'<paragraph>mn]o</paragraph>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>a[bc</paragraph>' +
+				'<listItem listIndent="0" listType="todo">def</listItem>' +
+				'<paragraph>ghi</paragraph>' +
+				'<listItem listIndent="0" listType="todo">jkl</listItem>' +
+				'<paragraph>mn]o</paragraph>'
+			);
+		} );
+
+		it( 'should mark all selected items as checked when at least one selected item is not checked', () => {
+			setModelData( model,
+				'<listItem listIndent="0" listType="todo">abc[</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">def</listItem>' +
+				'<listItem listIndent="0" listType="todo">]ghi</listItem>'
+			);
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">abc[</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">def</listItem>' +
+				'<listItem listIndent="0" listType="todo" todoListChecked="true">]ghi</listItem>'
+			);
+		} );
+
+		it( 'should do nothing when there is no elements to toggle attribute', () => {
+			setModelData( model, '<paragraph>b[]ar</paragraph>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal( '<paragraph>b[]ar</paragraph>' );
+		} );
+	} );
+} );