Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge cc87dba into 3d3c61f
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed Sep 16, 2019
2 parents 3d3c61f + cc87dba commit 7a89277
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 46 deletions.
52 changes: 14 additions & 38 deletions src/todolistconverters.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/* global document */

import { generateLiInUl, injectViewList, findInRange } from './utils';
import { findInRange, generateLiInUl, injectViewList } from './utils';
import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement';

/**
Expand Down Expand Up @@ -59,41 +59,6 @@ export function modelViewInsertion( model, onCheckboxChecked ) {
};
}

/**
* A model-to-view converter for the model `$text` element inside a to-do list item.
*
* It takes care of creating text after the {@link module:engine/view/uielement~UIElement checkbox UI element}.
*
* It is used by {@link module:engine/controller/editingcontroller~EditingController}.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
export function modelViewTextInsertion( evt, data, conversionApi ) {
const parent = data.range.start.parent;

if ( parent.name != 'listItem' || parent.getAttribute( 'listType' ) != 'todo' ) {
return;
}

if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {
return;
}

const viewWriter = conversionApi.writer;
const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
const viewText = viewWriter.createText( data.item.data );

// Be sure text is created after the UIElement, so if it is a first text node inside a `listItem` element
// it has to be moved after the first node in the view list item.
//
// model: <listItem listtype="todo">[foo]</listItem>
// view: <li>^<checkbox/></li> -> <li><checkbox/>foo</li>
viewWriter.insert( viewPosition.offset ? viewPosition : viewPosition.getShiftedBy( 1 ), viewText );
}

/**
* A model-to-view converter for the `listItem` model element insertion.
*
Expand Down Expand Up @@ -233,7 +198,7 @@ export function dataViewModelCheckmarkInsertion( evt, data, conversionApi ) {
* @param {Function} onCheckedChange Callback fired after clicking the checkbox UI element.
* @returns {Function} Returns a conversion callback.
*/
export function modelViewChangeType( onCheckedChange ) {
export function modelViewChangeType( onCheckedChange, view ) {
return ( evt, data, conversionApi ) => {
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;
Expand All @@ -246,7 +211,7 @@ export function modelViewChangeType( onCheckedChange ) {
viewWriter.insert( viewWriter.createPositionAt( viewItem, 0 ), checkmarkElement );
} else if ( data.attributeOldValue == 'todo' ) {
viewWriter.removeClass( 'todo-list', viewItem.parent );
viewWriter.remove( viewItem.getChild( 0 ) );
viewWriter.remove( findLabel( viewItem, view ) );
}
};
}
Expand Down Expand Up @@ -321,3 +286,14 @@ function createCheckmarkElement( modelItem, viewWriter, isChecked, onChange ) {

return uiElement;
}

// Helper method to find label element inside li.
export function findLabel( viewItem, view ) {
const range = view.createRangeIn( viewItem );

for ( const value of range ) {
if ( value.item.is( 'uiElement', 'label' ) ) {
return value.item;
}
}
}
29 changes: 25 additions & 4 deletions src/todolistediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import {
modelViewInsertion,
modelViewTextInsertion,
dataModelViewInsertion,
dataModelViewTextInsertion,
dataViewModelCheckmarkInsertion,
modelViewChangeChecked,
modelViewChangeType
modelViewChangeType,
findLabel
} from './todolistconverters';

import { findInRange } from './utils';
Expand Down Expand Up @@ -73,19 +73,40 @@ export default class TodoListEditing extends Plugin {
modelViewInsertion( model, listItem => this._handleCheckmarkChange( listItem ) ),
{ priority: 'high' }
);
editing.downcastDispatcher.on( 'insert:$text', modelViewTextInsertion, { priority: 'high' } );
data.downcastDispatcher.on( 'insert:listItem', dataModelViewInsertion( model ), { priority: 'high' } );
data.downcastDispatcher.on( 'insert:$text', dataModelViewTextInsertion, { priority: 'high' } );

editing.downcastDispatcher.on(
'attribute:listType:listItem',
modelViewChangeType( listItem => this._handleCheckmarkChange( listItem ) )
modelViewChangeType( listItem => this._handleCheckmarkChange( listItem ), editing.view )
);
editing.downcastDispatcher.on(
'attribute:todoListChecked:listItem',
modelViewChangeChecked( listItem => this._handleCheckmarkChange( listItem ) )
);

// Fix view position on 0 offset.
editing.mapper.on( 'modelToViewPosition', ( evt, data ) => {
const view = editing.view;
const modelPosition = data.modelPosition;

if ( modelPosition.parent.is( 'listItem' ) && modelPosition.parent.getAttribute( 'listType' ) == 'todo' ) {
const viewLi = editing.mapper.toViewElement( data.modelPosition.parent );

if ( modelPosition.offset === 0 ) {
const label = findLabel( viewLi, view );

if ( label ) {
if ( label.nextSibling ) {
data.viewPosition = view.createPositionAt( label.nextSibling, 0 );
} else {
data.viewPosition = view.createPositionAfter( label );
}
}
}
}
}, { priority: 'low' } );

data.upcastDispatcher.on( 'element:input', dataViewModelCheckmarkInsertion, { priority: 'high' } );

// Collect all view nodes that have changed and use it to check if the checkbox UI element is going to
Expand Down
5 changes: 3 additions & 2 deletions tests/manual/todo-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
import Link from '@ckeditor/ckeditor5-link/src/link';
import Table from '@ckeditor/ckeditor5-table/src/table';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
Expand All @@ -20,9 +21,9 @@ import TodoList from '../../src/todolist';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Enter, Typing, Heading, Highlight, Table, Bold, Paragraph, Undo, List, TodoList, Clipboard ],
plugins: [ Enter, Typing, Heading, Highlight, Table, Bold, Paragraph, Undo, List, TodoList, Clipboard, Link ],
toolbar: [
'heading', '|', 'bulletedList', 'numberedList', 'todoList', '|', 'bold', 'highlight', 'insertTable', '|', 'undo', 'redo'
'heading', '|', 'bulletedList', 'numberedList', 'todoList', '|', 'bold', 'link', 'highlight', 'insertTable', '|', 'undo', 'redo'
],
table: {
contentToolbar: [
Expand Down
80 changes: 78 additions & 2 deletions tests/todolistediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ListCommand from '../src/listcommand';
import TodoListCheckCommand from '../src/todolistcheckcommand';
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting';

import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
Expand All @@ -27,7 +28,7 @@ describe( 'TodoListEditing', () => {
beforeEach( () => {
return VirtualTestEditor
.create( {
plugins: [ TodoListEditing, Typing, BoldEditing, BlockQuoteEditing ]
plugins: [ TodoListEditing, Typing, BoldEditing, BlockQuoteEditing, LinkEditing ]
} )
.then( newEditor => {
editor = newEditor;
Expand Down Expand Up @@ -287,6 +288,42 @@ describe( 'TodoListEditing', () => {
);
} );

it( 'should properly convert list type change - inner text with attribute', () => {
setModelData( model,
'<listItem listType="todo" listIndent="0">1[.0</listItem>' +
'<listItem listType="todo" listIndent="0"><$text bold="true">2.0</$text></listItem>' +
'<listItem listType="todo" listIndent="0">3.]0</listItem>'
);

editor.execute( 'bulletedList' );

expect( getViewData( view ) ).to.equal(
'<ul>' +
'<li>1{.0</li>' +
'<li><strong>2.0</strong></li>' +
'<li>3.}0</li>' +
'</ul>'
);
} );

it( 'should properly convert list type change - inner text with many attributes', () => {
setModelData( model,
'<listItem listType="todo" listIndent="0">1[.0</listItem>' +
'<listItem listType="todo" listIndent="0"><$text bold="true" linkHref="foo">2.0</$text></listItem>' +
'<listItem listType="todo" listIndent="0">3.]0</listItem>'
);

editor.execute( 'bulletedList' );

expect( getViewData( view ) ).to.equal(
'<ul>' +
'<li>1{.0</li>' +
'<li><a href="foo"><strong>2.0</strong></a></li>' +
'<li>3.}0</li>' +
'</ul>'
);
} );

it( 'should convert todoListChecked attribute change', () => {
setModelData( model, '<listItem listType="todo" listIndent="0">1.0</listItem>' );

Expand Down Expand Up @@ -374,6 +411,46 @@ describe( 'TodoListEditing', () => {
model.change( writer => writer.setAttribute( 'todoListChecked', true, modelRoot.getChild( 0 ) ) );
expect( getViewData( view ) ).to.equal( '<test class="checked">{}Foo</test>' );
} );

it( 'should properly handle typing inside text node with attribute', () => {
setModelData( model, '<listItem listType="todo" listIndent="0"><$text bold="true">[]foo</$text></listItem>' );

editor.execute( 'input', { text: 'b' } );

expect( getModelData( model ) ).to.equal(
'<listItem listIndent="0" listType="todo"><$text bold="true">b[]foo</$text></listItem>'
);

expect( getViewData( view ) ).to.equal(
'<ul class="todo-list">' +
'<li>' +
'<label class="todo-list__checkmark" contenteditable="false"></label>' +
'<strong>b{}foo</strong>' +
'</li>' +
'</ul>'
);
} );

it( 'should properly handle typing inside text node with many attributes', () => {
setModelData( model,
'<listItem listType="todo" listIndent="0"><$text bold="true" linkHref="foo">[]foo</$text></listItem>'
);

editor.execute( 'input', { text: 'b' } );

expect( getModelData( model ) ).to.equal(
'<listItem listIndent="0" listType="todo"><$text bold="true" linkHref="foo">b[]foo</$text></listItem>'
);

expect( getViewData( view ) ).to.equal(
'<ul class="todo-list">' +
'<li>' +
'<label class="todo-list__checkmark" contenteditable="false"></label>' +
'<a href="foo"><strong>b{}foo</strong></a>' +
'</li>' +
'</ul>'
);
} );
} );

describe( 'data pipeline m -> v', () => {
Expand Down Expand Up @@ -741,7 +818,6 @@ describe( 'TodoListEditing', () => {
model: 'highlight',
view: { classes: 'highlight' }
} );

model.change( writer => {
writer.addMarker( 'element1', {
range: writer.createRangeIn( writer.createPositionAt( modelRoot.getChild( 0 ), 0 ) ),
Expand Down

0 comments on commit 7a89277

Please sign in to comment.