]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blame - sources/plugins/list/plugin.js
Update to 4.7.3
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / list / plugin.js
CommitLineData
c63493c8
IB
1/**\r
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.\r
3 * For licensing, see LICENSE.md or http://ckeditor.com/license\r
4 */\r
5\r
6/**\r
7 * @fileOverview Insert and remove numbered and bulleted lists.\r
8 */\r
9\r
10( function() {\r
11 var listNodeNames = { ol: 1, ul: 1 };\r
12\r
13 var whitespaces = CKEDITOR.dom.walker.whitespaces(),\r
14 bookmarks = CKEDITOR.dom.walker.bookmark(),\r
15 nonEmpty = function( node ) {\r
16 return !( whitespaces( node ) || bookmarks( node ) );\r
17 },\r
18 blockBogus = CKEDITOR.dom.walker.bogus();\r
19\r
20 function cleanUpDirection( element ) {\r
21 var dir, parent, parentDir;\r
22 if ( ( dir = element.getDirection() ) ) {\r
23 parent = element.getParent();\r
24 while ( parent && !( parentDir = parent.getDirection() ) )\r
25 parent = parent.getParent();\r
26\r
27 if ( dir == parentDir )\r
28 element.removeAttribute( 'dir' );\r
29 }\r
30 }\r
31\r
32 // Inherit inline styles from another element.\r
33 function inheritInlineStyles( parent, el ) {\r
34 var style = parent.getAttribute( 'style' );\r
35\r
36 // Put parent styles before child styles.\r
37 style && el.setAttribute( 'style', style.replace( /([^;])$/, '$1;' ) + ( el.getAttribute( 'style' ) || '' ) );\r
38 }\r
39\r
40 CKEDITOR.plugins.list = {\r
41 /**\r
42 * Convert a DOM list tree into a data structure that is easier to\r
43 * manipulate. This operation should be non-intrusive in the sense that it\r
44 * does not change the DOM tree, with the exception that it may add some\r
45 * markers to the list item nodes when database is specified.\r
46 *\r
47 * @member CKEDITOR.plugins.list\r
48 * @todo params\r
49 */\r
50 listToArray: function( listNode, database, baseArray, baseIndentLevel, grandparentNode ) {\r
51 if ( !listNodeNames[ listNode.getName() ] )\r
52 return [];\r
53\r
54 if ( !baseIndentLevel )\r
55 baseIndentLevel = 0;\r
56 if ( !baseArray )\r
57 baseArray = [];\r
58\r
59 // Iterate over all list items to and look for inner lists.\r
60 for ( var i = 0, count = listNode.getChildCount(); i < count; i++ ) {\r
61 var listItem = listNode.getChild( i );\r
62\r
1794320d 63 // Fixing malformed nested lists by moving it into a previous list item. (http://dev.ckeditor.com/ticket/6236)\r
c63493c8
IB
64 if ( listItem.type == CKEDITOR.NODE_ELEMENT && listItem.getName() in CKEDITOR.dtd.$list )\r
65 CKEDITOR.plugins.list.listToArray( listItem, database, baseArray, baseIndentLevel + 1 );\r
66\r
67 // It may be a text node or some funny stuff.\r
68 if ( listItem.$.nodeName.toLowerCase() != 'li' )\r
69 continue;\r
70\r
71 var itemObj = { 'parent': listNode, indent: baseIndentLevel, element: listItem, contents: [] };\r
72 if ( !grandparentNode ) {\r
73 itemObj.grandparent = listNode.getParent();\r
74 if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' )\r
75 itemObj.grandparent = itemObj.grandparent.getParent();\r
76 } else {\r
77 itemObj.grandparent = grandparentNode;\r
78 }\r
79\r
80 if ( database )\r
81 CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length );\r
82 baseArray.push( itemObj );\r
83\r
84 for ( var j = 0, itemChildCount = listItem.getChildCount(), child; j < itemChildCount; j++ ) {\r
85 child = listItem.getChild( j );\r
86 if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] )\r
87 // Note the recursion here, it pushes inner list items with\r
88 // +1 indentation in the correct order.\r
89 CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent );\r
90 else\r
91 itemObj.contents.push( child );\r
92 }\r
93 }\r
94 return baseArray;\r
95 },\r
96\r
97 /**\r
98 * Convert our internal representation of a list back to a DOM forest.\r
99 *\r
100 * @member CKEDITOR.plugins.list\r
101 * @todo params\r
102 */\r
103 arrayToList: function( listArray, database, baseIndex, paragraphMode, dir ) {\r
104 if ( !baseIndex )\r
105 baseIndex = 0;\r
106 if ( !listArray || listArray.length < baseIndex + 1 )\r
107 return null;\r
108\r
109 var i,\r
110 doc = listArray[ baseIndex ].parent.getDocument(),\r
111 retval = new CKEDITOR.dom.documentFragment( doc ),\r
112 rootNode = null,\r
113 currentIndex = baseIndex,\r
114 indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ),\r
115 currentListItem = null,\r
116 orgDir, block,\r
117 paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' );\r
118\r
119 while ( 1 ) {\r
120 var item = listArray[ currentIndex ],\r
121 itemGrandParent = item.grandparent;\r
122\r
123 orgDir = item.element.getDirection( 1 );\r
124\r
125 if ( item.indent == indentLevel ) {\r
126 if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() ) {\r
127 rootNode = listArray[ currentIndex ].parent.clone( false, 1 );\r
128 dir && rootNode.setAttribute( 'dir', dir );\r
129 retval.append( rootNode );\r
130 }\r
131 currentListItem = rootNode.append( item.element.clone( 0, 1 ) );\r
132\r
133 if ( orgDir != rootNode.getDirection( 1 ) )\r
134 currentListItem.setAttribute( 'dir', orgDir );\r
135\r
136 for ( i = 0; i < item.contents.length; i++ )\r
137 currentListItem.append( item.contents[ i ].clone( 1, 1 ) );\r
138 currentIndex++;\r
139 } else if ( item.indent == Math.max( indentLevel, 0 ) + 1 ) {\r
1794320d 140 // Maintain original direction (http://dev.ckeditor.com/ticket/6861).\r
c63493c8
IB
141 var currDir = listArray[ currentIndex - 1 ].element.getDirection( 1 ),\r
142 listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode, currDir != orgDir ? orgDir : null );\r
143\r
144 // If the next block is an <li> with another list tree as the first\r
145 // child, we'll need to append a filler (<br>/NBSP) or the list item\r
1794320d 146 // wouldn't be editable. (http://dev.ckeditor.com/ticket/6724)\r
c63493c8
IB
147 if ( !currentListItem.getChildCount() && CKEDITOR.env.needsNbspFiller && doc.$.documentMode <= 7 )\r
148 currentListItem.append( doc.createText( '\xa0' ) );\r
149 currentListItem.append( listData.listNode );\r
150 currentIndex = listData.nextIndex;\r
151 } else if ( item.indent == -1 && !baseIndex && itemGrandParent ) {\r
152 if ( listNodeNames[ itemGrandParent.getName() ] ) {\r
153 currentListItem = item.element.clone( false, true );\r
154 if ( orgDir != itemGrandParent.getDirection( 1 ) )\r
155 currentListItem.setAttribute( 'dir', orgDir );\r
156 } else {\r
157 currentListItem = new CKEDITOR.dom.documentFragment( doc );\r
158 }\r
159\r
160 // Migrate all children to the new container,\r
161 // apply the proper text direction.\r
162 var dirLoose = itemGrandParent.getDirection( 1 ) != orgDir,\r
163 li = item.element,\r
164 className = li.getAttribute( 'class' ),\r
165 style = li.getAttribute( 'style' );\r
166\r
167 var needsBlock = currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT && ( paragraphMode != CKEDITOR.ENTER_BR || dirLoose || style || className );\r
168\r
169 var child,\r
170 count = item.contents.length,\r
171 cachedBookmark;\r
172\r
173 for ( i = 0; i < count; i++ ) {\r
174 child = item.contents[ i ];\r
175\r
176 // Append bookmark if we can, or cache it and append it when we'll know\r
177 // what to do with it. Generally - we want to keep it next to its original neighbour.\r
178 // Exception: if bookmark is the only child it hasn't got any neighbour, so handle it normally\r
179 // (wrap with block if needed).\r
180 if ( bookmarks( child ) && count > 1 ) {\r
181 // If we don't need block, it's simple - append bookmark directly to the current list item.\r
182 if ( !needsBlock )\r
183 currentListItem.append( child.clone( 1, 1 ) );\r
184 else\r
185 cachedBookmark = child.clone( 1, 1 );\r
186 }\r
187 // Block content goes directly to the current list item, without wrapping.\r
188 else if ( child.type == CKEDITOR.NODE_ELEMENT && child.isBlockBoundary() ) {\r
189 // Apply direction on content blocks.\r
190 if ( dirLoose && !child.getDirection() )\r
191 child.setAttribute( 'dir', orgDir );\r
192\r
193 inheritInlineStyles( li, child );\r
194\r
195 className && child.addClass( className );\r
196\r
197 // Close the block which we started for inline content.\r
198 block = null;\r
199 // Append bookmark directly before current child.\r
200 if ( cachedBookmark ) {\r
201 currentListItem.append( cachedBookmark );\r
202 cachedBookmark = null;\r
203 }\r
204 // Append this block element to the list item.\r
205 currentListItem.append( child.clone( 1, 1 ) );\r
206 }\r
207 // Some inline content was found - wrap it with block and append that\r
208 // block to the current list item or append it to the block previously created.\r
209 else if ( needsBlock ) {\r
210 // Establish new block to hold text direction and styles.\r
211 if ( !block ) {\r
212 block = doc.createElement( paragraphName );\r
213 currentListItem.append( block );\r
214 dirLoose && block.setAttribute( 'dir', orgDir );\r
215 }\r
216\r
217 // Copy over styles to new block;\r
218 style && block.setAttribute( 'style', style );\r
219 className && block.setAttribute( 'class', className );\r
220\r
221 // Append bookmark directly before current child.\r
222 if ( cachedBookmark ) {\r
223 block.append( cachedBookmark );\r
224 cachedBookmark = null;\r
225 }\r
226 block.append( child.clone( 1, 1 ) );\r
227 }\r
228 // E.g. BR mode - inline content appended directly to the list item.\r
229 else {\r
230 currentListItem.append( child.clone( 1, 1 ) );\r
231 }\r
232 }\r
233\r
234 // No content after bookmark - append it to the block if we had one\r
235 // or directly to the current list item if we finished directly in the current list item.\r
236 if ( cachedBookmark ) {\r
237 ( block || currentListItem ).append( cachedBookmark );\r
238 cachedBookmark = null;\r
239 }\r
240\r
241 if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT && currentIndex != listArray.length - 1 ) {\r
242 var last;\r
243\r
244 // Remove bogus <br> if this browser uses them.\r
245 if ( CKEDITOR.env.needsBrFiller ) {\r
246 last = currentListItem.getLast();\r
247 if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( 'br' ) )\r
248 last.remove();\r
249 }\r
250\r
251 // If the last element is not a block, append <br> to separate merged list items.\r
252 last = currentListItem.getLast( nonEmpty );\r
253 if ( !( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( CKEDITOR.dtd.$block ) ) )\r
254 currentListItem.append( doc.createElement( 'br' ) );\r
255 }\r
256\r
257 var currentListItemName = currentListItem.$.nodeName.toLowerCase();\r
258 if ( currentListItemName == 'div' || currentListItemName == 'p' ) {\r
259 currentListItem.appendBogus();\r
260 }\r
261 retval.append( currentListItem );\r
262 rootNode = null;\r
263 currentIndex++;\r
264 } else {\r
265 return null;\r
266 }\r
267\r
268 block = null;\r
269\r
270 if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel )\r
271 break;\r
272 }\r
273\r
274 if ( database ) {\r
275 var currentNode = retval.getFirst();\r
276\r
277 while ( currentNode ) {\r
278 if ( currentNode.type == CKEDITOR.NODE_ELEMENT ) {\r
279 // Clear marker attributes for the new list tree made of cloned nodes, if any.\r
280 CKEDITOR.dom.element.clearMarkers( database, currentNode );\r
281\r
282 // Clear redundant direction attribute specified on list items.\r
283 if ( currentNode.getName() in CKEDITOR.dtd.$listItem )\r
284 cleanUpDirection( currentNode );\r
285 }\r
286\r
287 currentNode = currentNode.getNextSourceNode();\r
288 }\r
289 }\r
290\r
291 return { listNode: retval, nextIndex: currentIndex };\r
292 }\r
293 };\r
294\r
295 function changeListType( editor, groupObj, database, listsCreated ) {\r
296 // This case is easy...\r
297 // 1. Convert the whole list into a one-dimensional array.\r
298 // 2. Change the list type by modifying the array.\r
299 // 3. Recreate the whole list by converting the array to a list.\r
300 // 4. Replace the original list with the recreated list.\r
301 var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),\r
302 selectedListItems = [];\r
303\r
304 for ( var i = 0; i < groupObj.contents.length; i++ ) {\r
305 var itemNode = groupObj.contents[ i ];\r
306 itemNode = itemNode.getAscendant( 'li', true );\r
307 if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )\r
308 continue;\r
309 selectedListItems.push( itemNode );\r
310 CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );\r
311 }\r
312\r
313 var root = groupObj.root,\r
314 doc = root.getDocument(),\r
315 listNode, newListNode;\r
316\r
317 for ( i = 0; i < selectedListItems.length; i++ ) {\r
318 var listIndex = selectedListItems[ i ].getCustomData( 'listarray_index' );\r
319 listNode = listArray[ listIndex ].parent;\r
320\r
321 // Switch to new list node for this particular item.\r
322 if ( !listNode.is( this.type ) ) {\r
323 newListNode = doc.createElement( this.type );\r
324 // Copy all attributes, except from 'start' and 'type'.\r
325 listNode.copyAttributes( newListNode, { start: 1, type: 1 } );\r
326 // The list-style-type property should be ignored.\r
327 newListNode.removeStyle( 'list-style-type' );\r
328 listArray[ listIndex ].parent = newListNode;\r
329 }\r
330 }\r
331\r
332 var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode );\r
333 var child,\r
334 length = newList.listNode.getChildCount();\r
335 for ( i = 0; i < length && ( child = newList.listNode.getChild( i ) ); i++ ) {\r
336 if ( child.getName() == this.type )\r
337 listsCreated.push( child );\r
338 }\r
339 newList.listNode.replace( groupObj.root );\r
340\r
341 editor.fire( 'contentDomInvalidated' );\r
342 }\r
343\r
344 function createList( editor, groupObj, listsCreated ) {\r
345 var contents = groupObj.contents,\r
346 doc = groupObj.root.getDocument(),\r
347 listContents = [];\r
348\r
349 // It is possible to have the contents returned by DomRangeIterator to be the same as the root.\r
350 // e.g. when we're running into table cells.\r
351 // In such a case, enclose the childNodes of contents[0] into a <div>.\r
352 if ( contents.length == 1 && contents[ 0 ].equals( groupObj.root ) ) {\r
353 var divBlock = doc.createElement( 'div' );\r
354 contents[ 0 ].moveChildren && contents[ 0 ].moveChildren( divBlock );\r
355 contents[ 0 ].append( divBlock );\r
356 contents[ 0 ] = divBlock;\r
357 }\r
358\r
359 // Calculate the common parent node of all content blocks.\r
360 var commonParent = groupObj.contents[ 0 ].getParent();\r
361 for ( var i = 0; i < contents.length; i++ )\r
362 commonParent = commonParent.getCommonAncestor( contents[ i ].getParent() );\r
363\r
364 var useComputedState = editor.config.useComputedState,\r
365 listDir, explicitDirection;\r
366\r
367 useComputedState = useComputedState === undefined || useComputedState;\r
368\r
369 // We want to insert things that are in the same tree level only, so calculate the contents again\r
370 // by expanding the selected blocks to the same tree level.\r
371 for ( i = 0; i < contents.length; i++ ) {\r
372 var contentNode = contents[ i ],\r
373 parentNode;\r
374 while ( ( parentNode = contentNode.getParent() ) ) {\r
375 if ( parentNode.equals( commonParent ) ) {\r
376 listContents.push( contentNode );\r
377\r
378 // Determine the lists's direction.\r
379 if ( !explicitDirection && contentNode.getDirection() )\r
380 explicitDirection = 1;\r
381\r
382 var itemDir = contentNode.getDirection( useComputedState );\r
383\r
384 if ( listDir !== null ) {\r
385 // If at least one LI have a different direction than current listDir, we can't have listDir.\r
386 if ( listDir && listDir != itemDir )\r
387 listDir = null;\r
388 else\r
389 listDir = itemDir;\r
390 }\r
391\r
392 break;\r
393 }\r
394 contentNode = parentNode;\r
395 }\r
396 }\r
397\r
398 if ( listContents.length < 1 )\r
399 return;\r
400\r
401 // Insert the list to the DOM tree.\r
402 var insertAnchor = listContents[ listContents.length - 1 ].getNext(),\r
403 listNode = doc.createElement( this.type );\r
404\r
405 listsCreated.push( listNode );\r
406\r
407 var contentBlock, listItem;\r
408\r
409 while ( listContents.length ) {\r
410 contentBlock = listContents.shift();\r
411 listItem = doc.createElement( 'li' );\r
412\r
413 // If current block should be preserved, append it to list item instead of\r
414 // transforming it to <li> element.\r
415 if ( shouldPreserveBlock( contentBlock ) )\r
416 contentBlock.appendTo( listItem );\r
417 else {\r
418 contentBlock.copyAttributes( listItem );\r
1794320d 419 // Remove direction attribute after it was merged into list root. (http://dev.ckeditor.com/ticket/7657)\r
c63493c8
IB
420 if ( listDir && contentBlock.getDirection() ) {\r
421 listItem.removeStyle( 'direction' );\r
422 listItem.removeAttribute( 'dir' );\r
423 }\r
424 contentBlock.moveChildren( listItem );\r
425 contentBlock.remove();\r
426 }\r
427\r
428 listItem.appendTo( listNode );\r
429 }\r
430\r
431 // Apply list root dir only if it has been explicitly declared.\r
432 if ( listDir && explicitDirection )\r
433 listNode.setAttribute( 'dir', listDir );\r
434\r
435 if ( insertAnchor )\r
436 listNode.insertBefore( insertAnchor );\r
437 else\r
438 listNode.appendTo( commonParent );\r
439 }\r
440\r
441 function removeList( editor, groupObj, database ) {\r
442 // This is very much like the change list type operation.\r
443 // Except that we're changing the selected items' indent to -1 in the list array.\r
444 var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),\r
445 selectedListItems = [];\r
446\r
447 for ( var i = 0; i < groupObj.contents.length; i++ ) {\r
448 var itemNode = groupObj.contents[ i ];\r
449 itemNode = itemNode.getAscendant( 'li', true );\r
450 if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )\r
451 continue;\r
452 selectedListItems.push( itemNode );\r
453 CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );\r
454 }\r
455\r
456 var lastListIndex = null;\r
457 for ( i = 0; i < selectedListItems.length; i++ ) {\r
458 var listIndex = selectedListItems[ i ].getCustomData( 'listarray_index' );\r
459 listArray[ listIndex ].indent = -1;\r
460 lastListIndex = listIndex;\r
461 }\r
462\r
463 // After cutting parts of the list out with indent=-1, we still have to maintain the array list\r
464 // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the\r
465 // list cannot be converted back to a real DOM list.\r
466 for ( i = lastListIndex + 1; i < listArray.length; i++ ) {\r
467 if ( listArray[ i ].indent > listArray[ i - 1 ].indent + 1 ) {\r
468 var indentOffset = listArray[ i - 1 ].indent + 1 - listArray[ i ].indent;\r
469 var oldIndent = listArray[ i ].indent;\r
470 while ( listArray[ i ] && listArray[ i ].indent >= oldIndent ) {\r
471 listArray[ i ].indent += indentOffset;\r
472 i++;\r
473 }\r
474 i--;\r
475 }\r
476 }\r
477\r
478 var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, groupObj.root.getAttribute( 'dir' ) );\r
479\r
1794320d 480 // Compensate <br> before/after the list node if the surrounds are non-blocks.(http://dev.ckeditor.com/ticket/3836)\r
c63493c8
IB
481 var docFragment = newList.listNode,\r
482 boundaryNode, siblingNode;\r
483\r
484 function compensateBrs( isStart ) {\r
485 if (\r
486 ( boundaryNode = docFragment[ isStart ? 'getFirst' : 'getLast' ]() ) &&\r
487 !( boundaryNode.is && boundaryNode.isBlockBoundary() ) &&\r
488 ( siblingNode = groupObj.root[ isStart ? 'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.invisible( true ) ) ) &&\r
489 !( siblingNode.is && siblingNode.isBlockBoundary( { br: 1 } ) )\r
490 ) {\r
491 editor.document.createElement( 'br' )[ isStart ? 'insertBefore' : 'insertAfter' ]( boundaryNode );\r
492 }\r
493 }\r
494 compensateBrs( true );\r
495 compensateBrs();\r
496\r
497 docFragment.replace( groupObj.root );\r
498\r
499 editor.fire( 'contentDomInvalidated' );\r
500 }\r
501\r
502 var headerTagRegex = /^h[1-6]$/;\r
503\r
504 // Checks wheather this block should be element preserved (not transformed to <li>) when creating list.\r
505 function shouldPreserveBlock( block ) {\r
506 return (\r
1794320d 507 // http://dev.ckeditor.com/ticket/5335\r
c63493c8 508 block.is( 'pre' ) ||\r
1794320d 509 // http://dev.ckeditor.com/ticket/5271 - this is a header.\r
c63493c8
IB
510 headerTagRegex.test( block.getName() ) ||\r
511 // 11083 - this is a non-editable element.\r
512 block.getAttribute( 'contenteditable' ) == 'false'\r
513 );\r
514 }\r
515\r
516 function listCommand( name, type ) {\r
517 this.name = name;\r
518 this.type = type;\r
519 this.context = type;\r
520 this.allowedContent = type + ' li';\r
521 this.requiredContent = type;\r
522 }\r
523\r
524 var elementType = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT );\r
525\r
1794320d 526 // Merge child nodes with direction preserved. (http://dev.ckeditor.com/ticket/7448)\r
c63493c8
IB
527 function mergeChildren( from, into, refNode, forward ) {\r
528 var child, itemDir;\r
529 while ( ( child = from[ forward ? 'getLast' : 'getFirst' ]( elementType ) ) ) {\r
530 if ( ( itemDir = child.getDirection( 1 ) ) !== into.getDirection( 1 ) )\r
531 child.setAttribute( 'dir', itemDir );\r
532\r
533 child.remove();\r
534\r
535 refNode ? child[ forward ? 'insertBefore' : 'insertAfter' ]( refNode ) : into.append( child, forward );\r
536 }\r
537 }\r
538\r
539 listCommand.prototype = {\r
540 exec: function( editor ) {\r
541 // Run state check first of all.\r
542 this.refresh( editor, editor.elementPath() );\r
543\r
544 var config = editor.config,\r
545 selection = editor.getSelection(),\r
546 ranges = selection && selection.getRanges();\r
547\r
548 // Midas lists rule #1 says we can create a list even in an empty document.\r
549 // But DOM iterator wouldn't run if the document is really empty.\r
550 // So create a paragraph if the document is empty and we're going to create a list.\r
551 if ( this.state == CKEDITOR.TRISTATE_OFF ) {\r
552 var editable = editor.editable();\r
553 if ( !editable.getFirst( nonEmpty ) ) {\r
554 config.enterMode == CKEDITOR.ENTER_BR ? editable.appendBogus() : ranges[ 0 ].fixBlock( 1, config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );\r
555\r
556 selection.selectRanges( ranges );\r
557 }\r
558 // Maybe a single range there enclosing the whole list,\r
1794320d 559 // turn on the list state manually(http://dev.ckeditor.com/ticket/4129).\r
c63493c8
IB
560 else {\r
561 var range = ranges.length == 1 && ranges[ 0 ],\r
562 enclosedNode = range && range.getEnclosedNode();\r
563 if ( enclosedNode && enclosedNode.is && this.type == enclosedNode.getName() )\r
564 this.setState( CKEDITOR.TRISTATE_ON );\r
565 }\r
566 }\r
567\r
568 var bookmarks = selection.createBookmarks( true );\r
569\r
570 // Group the blocks up because there are many cases where multiple lists have to be created,\r
571 // or multiple lists have to be cancelled.\r
572 var listGroups = [],\r
573 database = {},\r
574 rangeIterator = ranges.createIterator(),\r
575 index = 0;\r
576\r
577 while ( ( range = rangeIterator.getNextRange() ) && ++index ) {\r
578 var boundaryNodes = range.getBoundaryNodes(),\r
579 startNode = boundaryNodes.startNode,\r
580 endNode = boundaryNodes.endNode;\r
581\r
582 if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' )\r
583 range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START );\r
584\r
585 if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' )\r
586 range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END );\r
587\r
588 var iterator = range.createIterator(),\r
589 block;\r
590\r
591 iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF );\r
592\r
593 while ( ( block = iterator.getNextParagraph() ) ) {\r
594 // Avoid duplicate blocks get processed across ranges.\r
595 if ( block.getCustomData( 'list_block' ) )\r
596 continue;\r
597 else\r
598 CKEDITOR.dom.element.setMarker( database, block, 'list_block', 1 );\r
599\r
600 var path = editor.elementPath( block ),\r
601 pathElements = path.elements,\r
602 pathElementsCount = pathElements.length,\r
603 processedFlag = 0,\r
604 blockLimit = path.blockLimit,\r
605 element;\r
606\r
607 // First, try to group by a list ancestor.\r
608 for ( var i = pathElementsCount - 1; i >= 0 && ( element = pathElements[ i ] ); i-- ) {\r
1794320d 609 // Don't leak outside block limit (http://dev.ckeditor.com/ticket/3940).\r
c63493c8
IB
610 if ( listNodeNames[ element.getName() ] && blockLimit.contains( element ) ) {\r
611 // If we've encountered a list inside a block limit\r
612 // The last group object of the block limit element should\r
613 // no longer be valid. Since paragraphs after the list\r
614 // should belong to a different group of paragraphs before\r
1794320d 615 // the list. (Bug http://dev.ckeditor.com/ticket/1309)\r
c63493c8
IB
616 blockLimit.removeCustomData( 'list_group_object_' + index );\r
617\r
618 var groupObj = element.getCustomData( 'list_group_object' );\r
619 if ( groupObj )\r
620 groupObj.contents.push( block );\r
621 else {\r
622 groupObj = { root: element, contents: [ block ] };\r
623 listGroups.push( groupObj );\r
624 CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj );\r
625 }\r
626 processedFlag = 1;\r
627 break;\r
628 }\r
629 }\r
630\r
631 if ( processedFlag )\r
632 continue;\r
633\r
634 // No list ancestor? Group by block limit, but don't mix contents from different ranges.\r
635 var root = blockLimit;\r
636 if ( root.getCustomData( 'list_group_object_' + index ) )\r
637 root.getCustomData( 'list_group_object_' + index ).contents.push( block );\r
638 else {\r
639 groupObj = { root: root, contents: [ block ] };\r
640 CKEDITOR.dom.element.setMarker( database, root, 'list_group_object_' + index, groupObj );\r
641 listGroups.push( groupObj );\r
642 }\r
643 }\r
644 }\r
645\r
646 // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.\r
647 // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking\r
648 // at the group that's not rooted at lists. So we have three cases to handle.\r
649 var listsCreated = [];\r
650 while ( listGroups.length > 0 ) {\r
651 groupObj = listGroups.shift();\r
652 if ( this.state == CKEDITOR.TRISTATE_OFF ) {\r
653 if ( listNodeNames[ groupObj.root.getName() ] )\r
654 changeListType.call( this, editor, groupObj, database, listsCreated );\r
655 else\r
656 createList.call( this, editor, groupObj, listsCreated );\r
657 } else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] ) {\r
658 removeList.call( this, editor, groupObj, database );\r
659 }\r
660 }\r
661\r
662 // For all new lists created, merge into adjacent, same type lists.\r
663 for ( i = 0; i < listsCreated.length; i++ )\r
664 mergeListSiblings( listsCreated[ i ] );\r
665\r
666 // Clean up, restore selection and update toolbar button states.\r
667 CKEDITOR.dom.element.clearAllMarkers( database );\r
668 selection.selectBookmarks( bookmarks );\r
669 editor.focus();\r
670 },\r
671\r
672 refresh: function( editor, path ) {\r
673 var list = path.contains( listNodeNames, 1 ),\r
674 limit = path.blockLimit || path.root;\r
675\r
676 // 1. Only a single type of list activate.\r
677 // 2. Do not show list outside of block limit.\r
678 if ( list && limit.contains( list ) )\r
679 this.setState( list.is( this.type ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );\r
680 else\r
681 this.setState( CKEDITOR.TRISTATE_OFF );\r
682 }\r
683 };\r
684\r
685 // Merge list adjacent, of same type lists.\r
686 function mergeListSiblings( listNode ) {\r
687\r
688 function mergeSibling( rtl ) {\r
689 var sibling = listNode[ rtl ? 'getPrevious' : 'getNext' ]( nonEmpty );\r
690 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.is( listNode.getName() ) ) {\r
1794320d 691 // Move children order by merge direction.(http://dev.ckeditor.com/ticket/3820)\r
c63493c8
IB
692 mergeChildren( listNode, sibling, null, !rtl );\r
693\r
694 listNode.remove();\r
695 listNode = sibling;\r
696 }\r
697 }\r
698\r
699 mergeSibling();\r
700 mergeSibling( 1 );\r
701 }\r
702\r
703 // Check if node is block element that recieves text.\r
704 function isTextBlock( node ) {\r
705 return node.type == CKEDITOR.NODE_ELEMENT && ( node.getName() in CKEDITOR.dtd.$block || node.getName() in CKEDITOR.dtd.$listItem ) && CKEDITOR.dtd[ node.getName() ][ '#' ];\r
706 }\r
707\r
708 // Join visually two block lines.\r
709 function joinNextLineToCursor( editor, cursor, nextCursor ) {\r
710 editor.fire( 'saveSnapshot' );\r
711\r
712 // Merge with previous block's content.\r
713 nextCursor.enlarge( CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS );\r
714 var frag = nextCursor.extractContents();\r
715\r
716 cursor.trim( false, true );\r
717 var bm = cursor.createBookmark();\r
718\r
719 // Kill original bogus;\r
720 var currentPath = new CKEDITOR.dom.elementPath( cursor.startContainer ),\r
721 pathBlock = currentPath.block,\r
722 currentBlock = currentPath.lastElement.getAscendant( 'li', 1 ) || pathBlock,\r
723 nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer ),\r
724 nextLi = nextPath.contains( CKEDITOR.dtd.$listItem ),\r
725 nextList = nextPath.contains( CKEDITOR.dtd.$list ),\r
726 last;\r
727\r
728 // Remove bogus node the current block/pseudo block.\r
729 if ( pathBlock ) {\r
730 var bogus = pathBlock.getBogus();\r
731 bogus && bogus.remove();\r
732 }\r
733 else if ( nextList ) {\r
734 last = nextList.getPrevious( nonEmpty );\r
735 if ( last && blockBogus( last ) )\r
736 last.remove();\r
737 }\r
738\r
739 // Kill the tail br in extracted.\r
740 last = frag.getLast();\r
741 if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( 'br' ) )\r
742 last.remove();\r
743\r
744 // Insert fragment at the range position.\r
745 var nextNode = cursor.startContainer.getChild( cursor.startOffset );\r
746 if ( nextNode )\r
747 frag.insertBefore( nextNode );\r
748 else\r
749 cursor.startContainer.append( frag );\r
750\r
751 // Move the sub list nested in the next list item.\r
752 if ( nextLi ) {\r
753 var sublist = getSubList( nextLi );\r
754 if ( sublist ) {\r
755 // If next line is in the sub list of the current list item.\r
756 if ( currentBlock.contains( nextLi ) ) {\r
757 mergeChildren( sublist, nextLi.getParent(), nextLi );\r
758 sublist.remove();\r
759 }\r
760 // Migrate the sub list to current list item.\r
761 else {\r
762 currentBlock.append( sublist );\r
763 }\r
764 }\r
765 }\r
766\r
767 var nextBlock, parent;\r
768 // Remove any remaining zombies path blocks at the end after line merged.\r
769 while ( nextCursor.checkStartOfBlock() && nextCursor.checkEndOfBlock() ) {\r
770 nextPath = nextCursor.startPath();\r
771 nextBlock = nextPath.block;\r
772\r
1794320d 773 // Abort when nothing to be removed (http://dev.ckeditor.com/ticket/10890).\r
c63493c8
IB
774 if ( !nextBlock )\r
775 break;\r
776\r
777 // Check if also to remove empty list.\r
778 if ( nextBlock.is( 'li' ) ) {\r
779 parent = nextBlock.getParent();\r
780 if ( nextBlock.equals( parent.getLast( nonEmpty ) ) && nextBlock.equals( parent.getFirst( nonEmpty ) ) )\r
781 nextBlock = parent;\r
782 }\r
783\r
784 nextCursor.moveToPosition( nextBlock, CKEDITOR.POSITION_BEFORE_START );\r
785 nextBlock.remove();\r
786 }\r
787\r
1794320d 788 // Check if need to further merge with the list resides after the merged block. (http://dev.ckeditor.com/ticket/9080)\r
c63493c8
IB
789 var walkerRng = nextCursor.clone(), editable = editor.editable();\r
790 walkerRng.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END );\r
791 var walker = new CKEDITOR.dom.walker( walkerRng );\r
792 walker.evaluator = function( node ) {\r
793 return nonEmpty( node ) && !blockBogus( node );\r
794 };\r
795 var next = walker.next();\r
796 if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getName() in CKEDITOR.dtd.$list )\r
797 mergeListSiblings( next );\r
798\r
799 cursor.moveToBookmark( bm );\r
800\r
801 // Make fresh selection.\r
802 cursor.select();\r
803\r
804 editor.fire( 'saveSnapshot' );\r
805 }\r
806\r
807 function getSubList( li ) {\r
808 var last = li.getLast( nonEmpty );\r
809 return last && last.type == CKEDITOR.NODE_ELEMENT && last.getName() in listNodeNames ? last : null;\r
810 }\r
811\r
812 CKEDITOR.plugins.add( 'list', {\r
813 // jscs:disable maximumLineLength\r
1794320d 814 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%\r
c63493c8
IB
815 // jscs:enable maximumLineLength\r
816 icons: 'bulletedlist,bulletedlist-rtl,numberedlist,numberedlist-rtl', // %REMOVE_LINE_CORE%\r
817 hidpi: true, // %REMOVE_LINE_CORE%\r
818 requires: 'indentlist',\r
819 init: function( editor ) {\r
820 if ( editor.blockless )\r
821 return;\r
822\r
823 // Register commands.\r
824 editor.addCommand( 'numberedlist', new listCommand( 'numberedlist', 'ol' ) );\r
825 editor.addCommand( 'bulletedlist', new listCommand( 'bulletedlist', 'ul' ) );\r
826\r
827 // Register the toolbar button.\r
828 if ( editor.ui.addButton ) {\r
829 editor.ui.addButton( 'NumberedList', {\r
830 label: editor.lang.list.numberedlist,\r
831 command: 'numberedlist',\r
832 directional: true,\r
833 toolbar: 'list,10'\r
834 } );\r
835 editor.ui.addButton( 'BulletedList', {\r
836 label: editor.lang.list.bulletedlist,\r
837 command: 'bulletedlist',\r
838 directional: true,\r
839 toolbar: 'list,20'\r
840 } );\r
841 }\r
842\r
1794320d 843 // Handled backspace/del key to join list items. (http://dev.ckeditor.com/ticket/8248,http://dev.ckeditor.com/ticket/9080)\r
c63493c8
IB
844 editor.on( 'key', function( evt ) {\r
845 // Use getKey directly in order to ignore modifiers.\r
846 // Justification: http://dev.ckeditor.com/ticket/11861#comment:13\r
847 var key = evt.data.domEvent.getKey(), li;\r
848\r
849 // DEl/BACKSPACE\r
850 if ( editor.mode == 'wysiwyg' && key in { 8: 1, 46: 1 } ) {\r
851 var sel = editor.getSelection(),\r
852 range = sel.getRanges()[ 0 ],\r
853 path = range && range.startPath();\r
854\r
855 if ( !range || !range.collapsed )\r
856 return;\r
857\r
858 var isBackspace = key == 8;\r
859 var editable = editor.editable();\r
860 var walker = new CKEDITOR.dom.walker( range.clone() );\r
861 walker.evaluator = function( node ) {\r
862 return nonEmpty( node ) && !blockBogus( node );\r
863 };\r
864 // Backspace/Del behavior at the start/end of table is handled in core.\r
865 walker.guard = function( node, isOut ) {\r
866 return !( isOut && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'table' ) );\r
867 };\r
868\r
869 var cursor = range.clone();\r
870\r
871 if ( isBackspace ) {\r
872 var previous, joinWith;\r
873\r
874 // Join a sub list's first line, with the previous visual line in parent.\r
875 if (\r
876 ( previous = path.contains( listNodeNames ) ) &&\r
877 range.checkBoundaryOfElement( previous, CKEDITOR.START ) &&\r
878 ( previous = previous.getParent() ) && previous.is( 'li' ) &&\r
879 ( previous = getSubList( previous ) )\r
880 ) {\r
881 joinWith = previous;\r
882 previous = previous.getPrevious( nonEmpty );\r
883 // Place cursor before the nested list.\r
884 cursor.moveToPosition(\r
885 previous && blockBogus( previous ) ? previous : joinWith,\r
886 CKEDITOR.POSITION_BEFORE_START );\r
887 }\r
888 // Join any line following a list, with the last visual line of the list.\r
889 else {\r
890 walker.range.setStartAt( editable, CKEDITOR.POSITION_AFTER_START );\r
891 walker.range.setEnd( range.startContainer, range.startOffset );\r
892\r
893 previous = walker.previous();\r
894\r
895 if (\r
896 previous && previous.type == CKEDITOR.NODE_ELEMENT &&\r
897 ( previous.getName() in listNodeNames ||\r
898 previous.is( 'li' ) )\r
899 ) {\r
900 if ( !previous.is( 'li' ) ) {\r
901 walker.range.selectNodeContents( previous );\r
902 walker.reset();\r
903 walker.evaluator = isTextBlock;\r
904 previous = walker.previous();\r
905 }\r
906\r
907 joinWith = previous;\r
908 // Place cursor at the end of previous block.\r
909 cursor.moveToElementEditEnd( joinWith );\r
910\r
1794320d 911 // And then just before end of closest block element (http://dev.ckeditor.com/ticket/12729).\r
c63493c8
IB
912 cursor.moveToPosition( cursor.endPath().block, CKEDITOR.POSITION_BEFORE_END );\r
913 }\r
914 }\r
915\r
916 if ( joinWith ) {\r
917 joinNextLineToCursor( editor, cursor, range );\r
918 evt.cancel();\r
919 }\r
920 else {\r
921 var list = path.contains( listNodeNames );\r
1794320d 922 // Backspace pressed at the start of list outdents the first list item. (http://dev.ckeditor.com/ticket/9129)\r
c63493c8
IB
923 if ( list && range.checkBoundaryOfElement( list, CKEDITOR.START ) ) {\r
924 li = list.getFirst( nonEmpty );\r
925\r
926 if ( range.checkBoundaryOfElement( li, CKEDITOR.START ) ) {\r
927 previous = list.getPrevious( nonEmpty );\r
928\r
929 // Only if the list item contains a sub list, do nothing but\r
930 // simply move cursor backward one character.\r
931 if ( getSubList( li ) ) {\r
932 if ( previous ) {\r
933 range.moveToElementEditEnd( previous );\r
934 range.select();\r
935 }\r
936\r
937 evt.cancel();\r
938 }\r
939 else {\r
940 editor.execCommand( 'outdent' );\r
941 evt.cancel();\r
942 }\r
943 }\r
944 }\r
945 }\r
946\r
947 } else {\r
948 var next, nextLine;\r
949\r
950 li = path.contains( 'li' );\r
951\r
952 if ( li ) {\r
953 walker.range.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END );\r
954\r
955 var last = li.getLast( nonEmpty );\r
956 var block = last && isTextBlock( last ) ? last : li;\r
957\r
958 // Indicate cursor at the visual end of an list item.\r
959 var isAtEnd = 0;\r
960\r
961 next = walker.next();\r
962\r
963 // When list item contains a sub list.\r
964 if (\r
965 next && next.type == CKEDITOR.NODE_ELEMENT &&\r
966 next.getName() in listNodeNames &&\r
967 next.equals( last )\r
968 ) {\r
969 isAtEnd = 1;\r
970\r
971 // Move to the first item in sub list.\r
972 next = walker.next();\r
973 }\r
974 // Right at the end of list item.\r
975 else if ( range.checkBoundaryOfElement( block, CKEDITOR.END ) ) {\r
976 isAtEnd = 2;\r
977 }\r
978\r
979 if ( isAtEnd && next ) {\r
980 // Put cursor range there.\r
981 nextLine = range.clone();\r
982 nextLine.moveToElementEditStart( next );\r
983\r
1794320d 984 // http://dev.ckeditor.com/ticket/13409\r
c63493c8
IB
985 // For the following case and similar\r
986 //\r
987 // <ul>\r
988 // <li>\r
989 // <p><a href="#one"><em>x^</em></a></p>\r
990 // <ul>\r
991 // <li><span>y</span></li>\r
992 // </ul>\r
993 // </li>\r
994 // </ul>\r
995 if ( isAtEnd == 1 ) {\r
996 // Move the cursor to <em> if attached to "x" text node.\r
997 cursor.optimize();\r
998\r
999 // Abort if the range is attached directly in <li>, like\r
1000 //\r
1001 // <ul>\r
1002 // <li>\r
1003 // x^\r
1004 // <ul>\r
1005 // <li><span>y</span></li>\r
1006 // </ul>\r
1007 // </li>\r
1008 // </ul>\r
1009 if ( !cursor.startContainer.equals( li ) ) {\r
1010 var node = cursor.startContainer,\r
1011 farthestInlineAscendant;\r
1012\r
1013 // Find <a>, which is farthest from <em> but still inline element.\r
1014 while ( node.is( CKEDITOR.dtd.$inline ) ) {\r
1015 farthestInlineAscendant = node;\r
1016 node = node.getParent();\r
1017 }\r
1018\r
1019 // Move the range so it does not contain inline elements.\r
1020 // It prevents <span> from being included in <em>.\r
1021 //\r
1022 // <ul>\r
1023 // <li>\r
1024 // <p><a href="#one"><em>x</em></a>^</p>\r
1025 // <ul>\r
1026 // <li><span>y</span></li>\r
1027 // </ul>\r
1028 // </li>\r
1029 // </ul>\r
1030 //\r
1031 // so instead of\r
1032 //\r
1033 // <ul>\r
1034 // <li>\r
1035 // <p><a href="#one"><em>x^<span>y</span></em></a></p>\r
1036 // </li>\r
1037 // </ul>\r
1038 //\r
1039 // pressing DELETE produces\r
1040 //\r
1041 // <ul>\r
1042 // <li>\r
1043 // <p><a href="#one"><em>x</em></a>^<span>y</span></p>\r
1044 // </li>\r
1045 // </ul>\r
1046 if ( farthestInlineAscendant ) {\r
1047 cursor.moveToPosition( farthestInlineAscendant, CKEDITOR.POSITION_AFTER_END );\r
1048 }\r
1049 }\r
1050 }\r
1051\r
1794320d 1052 // Moving `cursor` and `next line` only when at the end literally (http://dev.ckeditor.com/ticket/12729).\r
c63493c8
IB
1053 if ( isAtEnd == 2 ) {\r
1054 cursor.moveToPosition( cursor.endPath().block, CKEDITOR.POSITION_BEFORE_END );\r
1055\r
1056 // Next line might be text node not wrapped in block element.\r
1057 if ( nextLine.endPath().block ) {\r
1058 nextLine.moveToPosition( nextLine.endPath().block, CKEDITOR.POSITION_AFTER_START );\r
1059 }\r
1060 }\r
1061\r
1062 joinNextLineToCursor( editor, cursor, nextLine );\r
1063 evt.cancel();\r
1064 }\r
1065 } else {\r
1066 // Handle Del key pressed before the list.\r
1067 walker.range.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END );\r
1068 next = walker.next();\r
1069\r
1070 if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.is( listNodeNames ) ) {\r
1071 // The start <li>\r
1072 next = next.getFirst( nonEmpty );\r
1073\r
1074 // Simply remove the current empty block, move cursor to the\r
1075 // subsequent list.\r
1076 if ( path.block && range.checkStartOfBlock() && range.checkEndOfBlock() ) {\r
1077 path.block.remove();\r
1078 range.moveToElementEditStart( next );\r
1079 range.select();\r
1080 evt.cancel();\r
1081 }\r
1082 // Preventing the default (merge behavior), but simply move\r
1083 // the cursor one character forward if subsequent list item\r
1084 // contains sub list.\r
1085 else if ( getSubList( next ) ) {\r
1086 range.moveToElementEditStart( next );\r
1087 range.select();\r
1088 evt.cancel();\r
1089 }\r
1090 // Merge the first list item with the current line.\r
1091 else {\r
1092 nextLine = range.clone();\r
1093 nextLine.moveToElementEditStart( next );\r
1094 joinNextLineToCursor( editor, cursor, nextLine );\r
1095 evt.cancel();\r
1096 }\r
1097 }\r
1098 }\r
1099\r
1100 }\r
1101\r
1102 // The backspace/del could potentially put cursor at a bad position,\r
1103 // being it handled or not, check immediately the selection to have it fixed.\r
1104 setTimeout( function() {\r
1105 editor.selectionChange( 1 );\r
1106 } );\r
1107 }\r
1108 } );\r
1109 }\r
1110 } );\r
1111} )();\r