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

Commit

Permalink
Very basic implementation of todo list editing part.
Browse files Browse the repository at this point in the history
  • Loading branch information
oskarwrobel committed Jul 31, 2019
1 parent 345b977 commit 402484d
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 9 deletions.
58 changes: 53 additions & 5 deletions src/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ export function modelViewInsertion( model ) {
};
}

export function modelViewTextInsertion( evt, data, conversionApi ) {
const parent = data.range.start.parent;

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

conversionApi.consumable.consume( data.item, 'insert' );

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

viewWriter.insert( viewPosition.getShiftedBy( 1 ), viewText );
}

/**
* A model-to-view converter for `listItem` model element removal.
*
Expand Down Expand Up @@ -104,22 +120,36 @@ export function modelViewChangeType( evt, data, conversionApi ) {
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;

// 1. Break the container after and before the list item.
// Break the container after and before the list item.
// This will create a view list with one view list item -- the one that changed type.
viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );

// 2. Change name of the view list that holds the changed view item.
// Change name of the view list that holds the changed view item.
// We cannot just change name property, because that would not render properly.
let viewList = viewItem.parent;
const listName = data.attributeNewValue == 'numbered' ? 'ol' : 'ul';
viewList = viewWriter.rename( listName, viewList );

// 3. Merge the changed view list with other lists, if possible.
// Add or remove checkbox for toto list.
if ( data.attributeNewValue == 'todo' ) {
viewWriter.setAttribute( 'class', 'todo-list', viewList );
viewWriter.setAttribute( 'class', 'todo-list__item', viewItem );
viewWriter.insert(
viewWriter.createPositionAt( viewItem, 0 ),
viewWriter.createUIElement( 'input', { type: 'checkbox' } )
);
} else if ( data.attributeOldValue == 'todo' ) {
viewWriter.removeAttribute( 'class', viewList );
viewWriter.removeAttribute( 'class', viewItem );
viewWriter.remove( viewItem.getChild( 0 ) );
}

// Merge the changed view list with other lists, if possible.
mergeViewLists( viewWriter, viewList, viewList.nextSibling );
mergeViewLists( viewWriter, viewList.previousSibling, viewList );

// 4. Consumable insertion of children inside the item. They are already handled by re-building the item in view.
// Consumable insertion of children inside the item. They are already handled by re-building the item in view.
for ( const child of data.item.getChildren() ) {
conversionApi.consumable.consume( child, 'insert' );
}
Expand Down Expand Up @@ -794,8 +824,18 @@ function generateLiInUl( modelItem, conversionApi ) {
const viewItem = createViewListItemElement( viewWriter );

const viewList = viewWriter.createContainerElement( listType, null );

viewWriter.insert( viewWriter.createPositionAt( viewList, 0 ), viewItem );

if ( modelItem.getAttribute( 'listType' ) == 'todo' ) {
viewWriter.setAttribute( 'class', 'todo-list', viewList );
viewWriter.setAttribute( 'class', 'todo-list__item', viewItem );
viewWriter.insert(
viewWriter.createPositionAt( viewItem, 0 ),
viewWriter.createUIElement( 'input', { type: 'checkbox' } )
);
}

mapper.bindElements( modelItem, viewItem );

return viewItem;
Expand Down Expand Up @@ -917,7 +957,15 @@ function getSiblingListItem( modelItem, options ) {
// Helper function that takes two parameters, that are expected to be view list elements, and merges them.
// The merge happen only if both parameters are UL or OL elements.
function mergeViewLists( viewWriter, firstList, secondList ) {
if ( firstList && secondList && ( firstList.name == 'ul' || firstList.name == 'ol' ) && firstList.name == secondList.name ) {
if ( !firstList | !secondList ) {
return null;
}

if ( !( firstList.name == 'ul' || firstList.name == 'ol' ) ) {
return null;
}

if ( firstList.name == secondList.name && firstList.getAttribute( 'class' ) === secondList.getAttribute( 'class' ) ) {
return viewWriter.mergeContainers( viewWriter.createPositionAfter( firstList ) );
}

Expand Down
4 changes: 2 additions & 2 deletions src/listcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export default class ListCommand extends Command {
* The type of the list created by the command.
*
* @readonly
* @member {'numbered'|'bulleted'}
* @member {'numbered'|'bulleted'|'todo'}
*/
this.type = type == 'bulleted' ? 'bulleted' : 'numbered';
this.type = type;

/**
* A flag indicating whether the command is active, which means that the selection starts in a list of the same type.
Expand Down
21 changes: 20 additions & 1 deletion src/listediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
cleanList,
cleanListItem,
modelViewInsertion,
modelViewTextInsertion,
modelViewChangeType,
modelViewMergeAfter,
modelViewRemove,
Expand Down Expand Up @@ -74,6 +75,7 @@ export default class ListEditing extends Plugin {

editing.downcastDispatcher.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
editing.downcastDispatcher.on( 'insert:listItem', modelViewInsertion( editor.model ) );
editing.downcastDispatcher.on( 'insert:$text', modelViewTextInsertion );
data.downcastDispatcher.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
data.downcastDispatcher.on( 'insert:listItem', modelViewInsertion( editor.model ) );

Expand All @@ -95,16 +97,33 @@ export default class ListEditing extends Plugin {
// Fix indentation of pasted items.
editor.model.on( 'insertContent', modelIndentPasteFixer, { priority: 'high' } );

// Register commands for numbered and bulleted list.
// Register commands for ol list types: numbered, bulleted and todo.
editor.commands.add( 'numberedList', new ListCommand( editor, 'numbered' ) );
editor.commands.add( 'bulletedList', new ListCommand( editor, 'bulleted' ) );
editor.commands.add( 'todoList', new ListCommand( editor, 'todo' ) );

// Register commands for indenting.
editor.commands.add( 'indentList', new IndentCommand( editor, 'forward' ) );
editor.commands.add( 'outdentList', new IndentCommand( editor, 'backward' ) );

const viewDocument = this.editor.editing.view.document;

viewDocument.registerPostFixer( writer => {
if ( !viewDocument.selection.isCollapsed ) {
return false;
}

const position = viewDocument.selection.getFirstPosition();

if ( position.parent.name === 'li' && position.offset == 0 && position.nodeAfter && position.nodeAfter.is( 'uiElement' ) ) {
writer.setSelection( position.parent, 1 );

return true;
}

return false;
} );

// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
Expand Down
3 changes: 3 additions & 0 deletions src/listui.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import numberedListIcon from '../theme/icons/numberedlist.svg';
import bulletedListIcon from '../theme/icons/bulletedlist.svg';
import todoListIcon from '../theme/icons/todolist.svg';
import '../theme/list.css';

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
Expand All @@ -28,6 +30,7 @@ export default class ListUI extends Plugin {
const t = this.editor.t;
this._addButton( 'numberedList', t( 'Numbered List' ), numberedListIcon );
this._addButton( 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
this._addButton( 'todoList', t( 'Todo List' ), todoListIcon );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/manual/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import List from '../../src/list';
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Enter, Typing, Heading, Paragraph, Undo, List, Clipboard ],
toolbar: [ 'heading', '|', 'bulletedList', 'numberedList', 'undo', 'redo' ]
toolbar: [ 'heading', '|', 'bulletedList', 'numberedList', 'todoList', 'undo', 'redo' ]
} )
.then( editor => {
window.editor = editor;
Expand Down
1 change: 1 addition & 0 deletions theme/icons/todolist.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions theme/list.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.todo-list {
padding-left: 18px;

& > li {
list-style: none;

& > input {
margin-right: 7px;
}
}
}

0 comments on commit 402484d

Please sign in to comment.