8
0
Oskar Wróbel 6 лет назад
Родитель
Сommit
c0fd6ce44b

+ 17 - 22
packages/ckeditor5-list/src/todolistediting.js

@@ -81,14 +81,6 @@ export default class TodoListEditing extends Plugin {
 		this.listenTo( model, 'applyOperation', ( evt, args ) => {
 			const operation = args[ 0 ];
 
-			if ( operation.type != 'changeAttribute' && operation.key != 'listType' && operation.oldValue == 'todoList' ) {
-				for ( const item of operation.range.getItems() ) {
-					if ( item.name == 'listItem' && item.hasAttribute( 'todoListChecked' ) ) {
-						listItemsToFix.add( item );
-					}
-				}
-			}
-
 			if ( operation.type == 'rename' && operation.oldName == 'listItem' ) {
 				const item = operation.position.nodeAfter;
 
@@ -146,27 +138,31 @@ function moveUIElementsAfterCheckmark( writer, uiElements ) {
 // @param {module:engine/view/downcastwriter~DowncastWriter} writer
 // @param {module:engine/view/documentselection~DocumentSelection} selection
 function moveSelectionAfterCheckmark( writer, selection ) {
-	if ( !selection.isCollapsed ) {
-		return false;
-	}
+	const positionToChange = selection.getFirstPosition();
 
-	const position = selection.getFirstPosition();
-
-	if ( position.parent.name != 'li' || !position.parent.parent.hasClass( 'todo-list' ) ) {
+	if ( positionToChange.parent.name != 'li' || !positionToChange.parent.parent.hasClass( 'todo-list' ) ) {
 		return false;
 	}
 
-	const parentEndPosition = writer.createPositionAt( position.parent, 'end' );
-	const uiElement = findInRange( writer.createRange( position, parentEndPosition ), item => {
+	const parentEndPosition = writer.createPositionAt( positionToChange.parent, 'end' );
+	const uiElement = findInRange( writer.createRange( positionToChange, parentEndPosition ), item => {
 		return ( item.is( 'uiElement' ) && item.hasClass( 'todo-list__checkmark' ) ) ? item : false;
 	} );
 
-	if ( uiElement && !position.isAfter( writer.createPositionBefore( uiElement ) ) ) {
-		const range = writer.createRange( writer.createPositionAfter( uiElement ), parentEndPosition );
-		const text = findInRange( range, item => item.is( 'textProxy' ) ? item.textNode : false );
+	if ( uiElement && !positionToChange.isAfter( writer.createPositionBefore( uiElement ) ) ) {
+		const boundaries = writer.createRange( writer.createPositionAfter( uiElement ), parentEndPosition );
+		const text = findInRange( boundaries, item => item.is( 'textProxy' ) ? item.textNode : false );
 		const nextPosition = text ? writer.createPositionAt( text, 0 ) : parentEndPosition;
 
-		writer.setSelection( nextPosition );
+		let range;
+
+		if ( selection.isCollapsed ) {
+			range = writer.createRange( nextPosition );
+		} else {
+			range = writer.createRange( nextPosition, selection.getLastPosition() );
+		}
+
+		writer.setSelection( range, { isBackward: selection.isBackward } );
 
 		return true;
 	}
@@ -189,11 +185,10 @@ function jumpOverCheckmarkOnLeftArrowKeyPress( stopKeyEvent, model ) {
 	const parent = position.parent;
 
 	if ( parent.name === 'listItem' && parent.getAttribute( 'listType' ) == 'todo' && position.isAtStart ) {
-		stopKeyEvent();
-
 		const newRange = schema.getNearestSelectionRange( model.createPositionBefore( parent ), 'backward' );
 
 		if ( newRange ) {
+			stopKeyEvent();
 			model.change( writer => writer.setSelection( newRange ) );
 		}
 	}

+ 136 - 11
packages/ckeditor5-list/tests/todolistediting.js

@@ -6,12 +6,14 @@
 import TodoListEditing from '../src/todolistediting';
 import ListEditing from '../src/listediting';
 import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
+import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import ListCommand from '../src/listcommand';
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
 
 describe( 'TodoListEditing', () => {
 	let editor, model, modelDoc, modelRoot, view, viewDoc, viewRoot;
@@ -19,7 +21,7 @@ describe( 'TodoListEditing', () => {
 	beforeEach( () => {
 		return VirtualTestEditor
 			.create( {
-				plugins: [ TodoListEditing, Typing, BoldEditing ]
+				plugins: [ TodoListEditing, Typing, BoldEditing, BlockQuoteEditing ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -294,6 +296,14 @@ describe( 'TodoListEditing', () => {
 			);
 		} );
 
+		it( 'should remove todoListChecked attribute when checked todoListItem is changed to regular list item', () => {
+			setModelData( model, '<listItem listType="todo" listIndent="0" todoListChecked="true">fo[]o</listItem>' );
+
+			editor.execute( 'bulletedList' );
+
+			expect( getModelData( model ) ).to.equal( '<listItem listIndent="0" listType="bulleted">fo[]o</listItem>' );
+		} );
+
 		it( 'should be overwritable', () => {
 			editor.editing.downcastDispatcher.on( 'insert:listItem', ( evt, data, api ) => {
 				const { consumable, writer, mapper } = api;
@@ -527,49 +537,164 @@ describe( 'TodoListEditing', () => {
 				'</ul>'
 			);
 		} );
+
+		it( 'should move start of none-collapsed selection after checkmark element to the first text node', () => {
+			setModelData( model, '<listItem listType="todo" listIndent="0">[Foo]</listItem>' );
+
+			expect( getViewData( view ) ).to.equal(
+				'<ul class="todo-list">' +
+					'<li><label class="todo-list__checkmark" contenteditable="false"></label>{Foo}</li>' +
+				'</ul>'
+			);
+		} );
+
+		it( 'should move start of none-collapsed, backward selection after checkmark element to the first text node', () => {
+			setModelData( model, '<listItem listType="todo" listIndent="0">[Foo]</listItem>', { lastRangeBackward: true } );
+
+			expect( getViewData( view ) ).to.equal(
+				'<ul class="todo-list">' +
+				'<li><label class="todo-list__checkmark" contenteditable="false"></label>{Foo}</li>' +
+				'</ul>'
+			);
+		} );
 	} );
 
 	describe( 'uiElements view post-fixer', () => {
 		it( 'should move all UIElements from before a checkmark after the checkmark element', () => {
-			setModelData( model, '<listItem listType="todo" listIndent="0">foo</listItem>' );
+			setModelData( model,
+				'<listItem listType="todo" listIndent="0">foo</listItem>' +
+				'<listItem listType="todo" listIndent="0">bar</listItem>'
+			);
 
 			editor.conversion.for( 'downcast' ).markerToElement( {
-				model: 'foo',
-				view: ( data, writer ) => writer.createUIElement( 'something' )
+				model: 'element1',
+				view: ( data, writer ) => writer.createUIElement( 'element1' )
+			} );
+
+			editor.conversion.for( 'downcast' ).markerToElement( {
+				model: 'element2',
+				view: ( data, writer ) => writer.createUIElement( 'element2' )
 			} );
 
 			editor.conversion.for( 'downcast' ).markerToHighlight( {
-				model: 'bar',
-				view: { classes: 'bar' }
+				model: 'highlight',
+				view: { classes: 'highlight' }
 			} );
 
 			model.change( writer => {
-				writer.addMarker( 'foo', {
+				writer.addMarker( 'element1', {
 					range: writer.createRangeIn( writer.createPositionAt( modelRoot.getChild( 0 ), 0 ) ),
 					usingOperation: false
 				} );
 
-				writer.addMarker( 'bar', {
+				writer.addMarker( 'element2', {
+					range: writer.createRangeIn( writer.createPositionAt( modelRoot.getChild( 0 ), 0 ) ),
+					usingOperation: false
+				} );
+
+				writer.addMarker( 'highlight', {
 					range: writer.createRangeIn( modelRoot.getChild( 0 ) ),
 					usingOperation: false
 				} );
 
 				// VirtualTestEeditor does not render V to DOM, so we need to mock element market to be rendered
 				// because view post-fixer uses it.
-				view._renderer.markedChildren = new Set( [ viewRoot.getChild( 0 ).getChild( 0 ) ] );
+				view._renderer.markedChildren = new Set( [
+					viewRoot.getChild( 0 ).getChild( 0 ),
+					viewRoot.getChild( 0 ).getChild( 1 )
+				] );
 			} );
 
 			expect( getViewData( view ) ).to.equal(
 				'<ul class="todo-list">' +
 					'<li>' +
-						'<span class="bar">' +
+						'<span class="highlight">' +
 							'<label class="todo-list__checkmark" contenteditable="false"></label>' +
-							'<something></something>' +
+							'<element1></element1>' +
+							'<element2></element2>' +
 							'{}foo' +
 						'</span>' +
 					'</li>' +
+					'<li>' +
+						'<label class="todo-list__checkmark" contenteditable="false"></label>bar' +
+					'</li>' +
 				'</ul>'
 			);
 		} );
 	} );
+
+	describe( 'todoListChecked attribute model post-fixer', () => {
+		it( 'should remove todoListChecked attribute when checked todoListItem is renamed', () => {
+			setModelData( model, '<listItem listType="todo" listIndent="0" todoListChecked="true">fo[]o</listItem>' );
+
+			editor.execute( 'todoList' );
+
+			expect( getModelData( model ) ).to.equal( '<paragraph>fo[]o</paragraph>' );
+		} );
+	} );
+
+	describe( 'leftArrow key handling', () => {
+		let domEvtDataStub;
+
+		beforeEach( () => {
+			domEvtDataStub = {
+				keyCode: getCode( 'arrowLeft' ),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy(),
+				domTarget: {
+					ownerDocument: {
+						defaultView: {
+							getSelection: () => ( { rangeCount: 0 } )
+						}
+					}
+				}
+			};
+		} );
+
+		it( 'should jump at the end of the previous node when selection is after checkmark element', () => {
+			setModelData( model,
+				'<blockQuote><paragraph>foo</paragraph></blockQuote>' +
+				'<listItem listIndent="0" listType="todo">[]bar</listItem>'
+			);
+
+			viewDoc.fire( 'keydown', domEvtDataStub );
+
+			expect( getModelData( model ) ).to.equal(
+				'<blockQuote><paragraph>foo[]</paragraph></blockQuote>' +
+				'<listItem listIndent="0" listType="todo">bar</listItem>'
+			);
+
+			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
+			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
+		} );
+
+		it( 'should do nothing when list item is a first block element in the root', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">[]bar</listItem>' );
+
+			viewDoc.fire( 'keydown', domEvtDataStub );
+
+			sinon.assert.notCalled( domEvtDataStub.preventDefault );
+			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
+
+			expect( getModelData( model ) ).to.equal( '<listItem listIndent="0" listType="todo">[]bar</listItem>' );
+		} );
+
+		it( 'should do nothing when selection is not collapsed', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">[bar]</listItem>' );
+
+			viewDoc.fire( 'keydown', domEvtDataStub );
+
+			sinon.assert.notCalled( domEvtDataStub.preventDefault );
+			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
+		} );
+
+		it( 'should do nothing when selection is not at the beginning list item', () => {
+			setModelData( model, '<listItem listIndent="0" listType="todo">b[]ar</listItem>' );
+
+			viewDoc.fire( 'keydown', domEvtDataStub );
+
+			sinon.assert.notCalled( domEvtDataStub.preventDefault );
+			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
+		} );
+	} );
 } );