]> git.immae.eu Git - perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git/blame - sources/plugins/enterkey/plugin.js
Upgrade to 4.5.7 and add some plugin
[perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git] / sources / plugins / enterkey / plugin.js
CommitLineData
7adcb81e 1/**\r
3b35bd27 2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.\r
7adcb81e
IB
3 * For licensing, see LICENSE.md or http://ckeditor.com/license\r
4 */\r
5\r
6( function() {\r
7 CKEDITOR.plugins.add( 'enterkey', {\r
8 init: function( editor ) {\r
9 editor.addCommand( 'enter', {\r
10 modes: { wysiwyg: 1 },\r
11 editorFocus: false,\r
12 exec: function( editor ) {\r
13 enter( editor );\r
14 }\r
15 } );\r
16\r
17 editor.addCommand( 'shiftEnter', {\r
18 modes: { wysiwyg: 1 },\r
19 editorFocus: false,\r
20 exec: function( editor ) {\r
21 shiftEnter( editor );\r
22 }\r
23 } );\r
24\r
25 editor.setKeystroke( [\r
26 [ 13, 'enter' ],\r
27 [ CKEDITOR.SHIFT + 13, 'shiftEnter' ]\r
28 ] );\r
29 }\r
30 } );\r
31\r
32 var whitespaces = CKEDITOR.dom.walker.whitespaces(),\r
33 bookmark = CKEDITOR.dom.walker.bookmark();\r
34\r
35 CKEDITOR.plugins.enterkey = {\r
36 enterBlock: function( editor, mode, range, forceMode ) {\r
37 // Get the range for the current selection.\r
38 range = range || getRange( editor );\r
39\r
40 // We may not have valid ranges to work on, like when inside a\r
41 // contenteditable=false element.\r
42 if ( !range )\r
43 return;\r
44\r
45 // When range is in nested editable, we have to replace range with this one,\r
46 // which have root property set to closest editable, to make auto paragraphing work. (#12162)\r
47 range = replaceRangeWithClosestEditableRoot( range );\r
48\r
49 var doc = range.document;\r
50\r
51 var atBlockStart = range.checkStartOfBlock(),\r
52 atBlockEnd = range.checkEndOfBlock(),\r
53 path = editor.elementPath( range.startContainer ),\r
54 block = path.block,\r
55\r
56 // Determine the block element to be used.\r
57 blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ),\r
58\r
59 newBlock;\r
60\r
61 // Exit the list when we're inside an empty list item block. (#5376)\r
62 if ( atBlockStart && atBlockEnd ) {\r
63 // Exit the list when we're inside an empty list item block. (#5376)\r
64 if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) ) {\r
65 // Make sure to point to the li when dealing with empty list item.\r
66 if ( !block.is( 'li' ) )\r
67 block = block.getParent();\r
68\r
69 var blockParent = block.getParent(),\r
70 blockGrandParent = blockParent.getParent(),\r
71\r
72 firstChild = !block.hasPrevious(),\r
73 lastChild = !block.hasNext(),\r
74\r
75 selection = editor.getSelection(),\r
76 bookmarks = selection.createBookmarks(),\r
77\r
78 orgDir = block.getDirection( 1 ),\r
79 className = block.getAttribute( 'class' ),\r
80 style = block.getAttribute( 'style' ),\r
81 dirLoose = blockGrandParent.getDirection( 1 ) != orgDir,\r
82\r
83 enterMode = editor.enterMode,\r
84 needsBlock = enterMode != CKEDITOR.ENTER_BR || dirLoose || style || className,\r
85\r
86 child;\r
87\r
88 if ( blockGrandParent.is( 'li' ) ) {\r
89\r
90 // If block is the first or the last child of the parent\r
91 // list, degrade it and move to the outer list:\r
92 // before the parent list if block is first child and after\r
93 // the parent list if block is the last child, respectively.\r
94 //\r
95 // <ul> => <ul>\r
96 // <li> => <li>\r
97 // <ul> => <ul>\r
98 // <li>x</li> => <li>x</li>\r
99 // <li>^</li> => </ul>\r
100 // </ul> => </li>\r
101 // </li> => <li>^</li>\r
102 // </ul> => </ul>\r
103 //\r
104 // AND\r
105 //\r
106 // <ul> => <ul>\r
107 // <li> => <li>^</li>\r
108 // <ul> => <li>\r
109 // <li>^</li> => <ul>\r
110 // <li>x</li> => <li>x</li>\r
111 // </ul> => </ul>\r
112 // </li> => </li>\r
113 // </ul> => </ul>\r
114\r
115 if ( firstChild || lastChild ) {\r
116\r
117 // If it's only child, we don't want to keep perent ul anymore.\r
118 if ( firstChild && lastChild ) {\r
119 blockParent.remove();\r
120 }\r
121\r
122 block[lastChild ? 'insertAfter' : 'insertBefore']( blockGrandParent );\r
123\r
124 // If the empty block is neither first nor last child\r
125 // then split the list and the block as an element\r
126 // of outer list.\r
127 //\r
128 // => <ul>\r
129 // => <li>\r
130 // <ul> => <ul>\r
131 // <li> => <li>x</li>\r
132 // <ul> => </ul>\r
133 // <li>x</li> => </li>\r
134 // <li>^</li> => <li>^</li>\r
135 // <li>y</li> => <li>\r
136 // </ul> => <ul>\r
137 // </li> => <li>y</li>\r
138 // </ul> => </ul>\r
139 // => </li>\r
140 // => </ul>\r
141\r
142 } else {\r
143 block.breakParent( blockGrandParent );\r
144 }\r
145 }\r
146\r
147 else if ( !needsBlock ) {\r
148 block.appendBogus( true );\r
149\r
150 // If block is the first or last child of the parent\r
151 // list, move all block's children out of the list:\r
152 // before the list if block is first child and after the list\r
153 // if block is the last child, respectively.\r
154 //\r
155 // <ul> => <ul>\r
156 // <li>x</li> => <li>x</li>\r
157 // <li>^</li> => </ul>\r
158 // </ul> => ^\r
159 //\r
160 // AND\r
161 //\r
162 // <ul> => ^\r
163 // <li>^</li> => <ul>\r
164 // <li>x</li> => <li>x</li>\r
165 // </ul> => </ul>\r
166\r
167 if ( firstChild || lastChild ) {\r
168 while ( ( child = block[ firstChild ? 'getFirst' : 'getLast' ]() ) )\r
169 child[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent );\r
170 }\r
171\r
172 // If the empty block is neither first nor last child\r
173 // then split the list and put all the block contents\r
174 // between two lists.\r
175 //\r
176 // <ul> => <ul>\r
177 // <li>x</li> => <li>x</li>\r
178 // <li>^</li> => </ul>\r
179 // <li>y</li> => ^\r
180 // </ul> => <ul>\r
181 // => <li>y</li>\r
182 // => </ul>\r
183\r
184 else {\r
185 block.breakParent( blockParent );\r
186\r
187 while ( ( child = block.getLast() ) )\r
188 child.insertAfter( blockParent );\r
189 }\r
190\r
191 block.remove();\r
192 } else {\r
193 // Original path block is the list item, create new block for the list item content.\r
194 if ( path.block.is( 'li' ) ) {\r
195 // Use <div> block for ENTER_BR and ENTER_DIV.\r
196 newBlock = doc.createElement( mode == CKEDITOR.ENTER_P ? 'p' : 'div' );\r
197\r
198 if ( dirLoose )\r
199 newBlock.setAttribute( 'dir', orgDir );\r
200\r
201 style && newBlock.setAttribute( 'style', style );\r
202 className && newBlock.setAttribute( 'class', className );\r
203\r
204 // Move all the child nodes to the new block.\r
205 block.moveChildren( newBlock );\r
206 }\r
207 // The original path block is not a list item, just copy the block to out side of the list.\r
208 else {\r
209 newBlock = path.block;\r
210 }\r
211\r
212 // If block is the first or last child of the parent\r
213 // list, move it out of the list:\r
214 // before the list if block is first child and after the list\r
215 // if block is the last child, respectively.\r
216 //\r
217 // <ul> => <ul>\r
218 // <li>x</li> => <li>x</li>\r
219 // <li>^</li> => </ul>\r
220 // </ul> => <p>^</p>\r
221 //\r
222 // AND\r
223 //\r
224 // <ul> => <p>^</p>\r
225 // <li>^</li> => <ul>\r
226 // <li>x</li> => <li>x</li>\r
227 // </ul> => </ul>\r
228\r
229 if ( firstChild || lastChild )\r
230 newBlock[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent );\r
231\r
232 // If the empty block is neither first nor last child\r
233 // then split the list and put the new block between\r
234 // two lists.\r
235 //\r
236 // => <ul>\r
237 // <ul> => <li>x</li>\r
238 // <li>x</li> => </ul>\r
239 // <li>^</li> => <p>^</p>\r
240 // <li>y</li> => <ul>\r
241 // </ul> => <li>y</li>\r
242 // => </ul>\r
243\r
244 else {\r
245 block.breakParent( blockParent );\r
246 newBlock.insertAfter( blockParent );\r
247 }\r
248\r
249 block.remove();\r
250 }\r
251\r
252 selection.selectBookmarks( bookmarks );\r
253\r
254 return;\r
255 }\r
256\r
257 if ( block && block.getParent().is( 'blockquote' ) ) {\r
258 block.breakParent( block.getParent() );\r
259\r
260 // If we were at the start of <blockquote>, there will be an empty element before it now.\r
261 if ( !block.getPrevious().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) )\r
262 block.getPrevious().remove();\r
263\r
264 // If we were at the end of <blockquote>, there will be an empty element after it now.\r
265 if ( !block.getNext().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) )\r
266 block.getNext().remove();\r
267\r
268 range.moveToElementEditStart( block );\r
269 range.select();\r
270 return;\r
271 }\r
272 }\r
273 // Don't split <pre> if we're in the middle of it, act as shift enter key.\r
274 else if ( block && block.is( 'pre' ) ) {\r
275 if ( !atBlockEnd ) {\r
276 enterBr( editor, mode, range, forceMode );\r
277 return;\r
278 }\r
279 }\r
280\r
281 // Split the range.\r
282 var splitInfo = range.splitBlock( blockTag );\r
283\r
284 if ( !splitInfo )\r
285 return;\r
286\r
287 // Get the current blocks.\r
288 var previousBlock = splitInfo.previousBlock,\r
289 nextBlock = splitInfo.nextBlock;\r
290\r
291 var isStartOfBlock = splitInfo.wasStartOfBlock,\r
292 isEndOfBlock = splitInfo.wasEndOfBlock;\r
293\r
294 var node;\r
295\r
296 // If this is a block under a list item, split it as well. (#1647)\r
297 if ( nextBlock ) {\r
298 node = nextBlock.getParent();\r
299 if ( node.is( 'li' ) ) {\r
300 nextBlock.breakParent( node );\r
301 nextBlock.move( nextBlock.getNext(), 1 );\r
302 }\r
303 } else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) ) {\r
304 previousBlock.breakParent( node );\r
305 node = previousBlock.getNext();\r
306 range.moveToElementEditStart( node );\r
307 previousBlock.move( previousBlock.getPrevious() );\r
308 }\r
309\r
310 // If we have both the previous and next blocks, it means that the\r
311 // boundaries were on separated blocks, or none of them where on the\r
312 // block limits (start/end).\r
313 if ( !isStartOfBlock && !isEndOfBlock ) {\r
314 // If the next block is an <li> with another list tree as the first\r
315 // child, we'll need to append a filler (<br>/NBSP) or the list item\r
316 // wouldn't be editable. (#1420)\r
317 if ( nextBlock.is( 'li' ) ) {\r
318 var walkerRange = range.clone();\r
319 walkerRange.selectNodeContents( nextBlock );\r
320 var walker = new CKEDITOR.dom.walker( walkerRange );\r
321 walker.evaluator = function( node ) {\r
322 return !( bookmark( node ) || whitespaces( node ) || node.type == CKEDITOR.NODE_ELEMENT && node.getName() in CKEDITOR.dtd.$inline && !( node.getName() in CKEDITOR.dtd.$empty ) );\r
323 };\r
324\r
325 node = walker.next();\r
326 if ( node && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'ul', 'ol' ) )\r
327 ( CKEDITOR.env.needsBrFiller ? doc.createElement( 'br' ) : doc.createText( '\xa0' ) ).insertBefore( node );\r
328 }\r
329\r
330 // Move the selection to the end block.\r
331 if ( nextBlock )\r
332 range.moveToElementEditStart( nextBlock );\r
333 } else {\r
334 var newBlockDir;\r
335\r
336 if ( previousBlock ) {\r
337 // Do not enter this block if it's a header tag, or we are in\r
338 // a Shift+Enter (#77). Create a new block element instead\r
339 // (later in the code).\r
340 if ( previousBlock.is( 'li' ) || !( headerTagRegex.test( previousBlock.getName() ) || previousBlock.is( 'pre' ) ) ) {\r
341 // Otherwise, duplicate the previous block.\r
342 newBlock = previousBlock.clone();\r
343 }\r
344 } else if ( nextBlock ) {\r
345 newBlock = nextBlock.clone();\r
346 }\r
347\r
348 if ( !newBlock ) {\r
349 // We have already created a new list item. (#6849)\r
350 if ( node && node.is( 'li' ) )\r
351 newBlock = node;\r
352 else {\r
353 newBlock = doc.createElement( blockTag );\r
354 if ( previousBlock && ( newBlockDir = previousBlock.getDirection() ) )\r
355 newBlock.setAttribute( 'dir', newBlockDir );\r
356 }\r
357 }\r
358 // Force the enter block unless we're talking of a list item.\r
359 else if ( forceMode && !newBlock.is( 'li' ) ) {\r
360 newBlock.renameNode( blockTag );\r
361 }\r
362\r
363 // Recreate the inline elements tree, which was available\r
364 // before hitting enter, so the same styles will be available in\r
365 // the new block.\r
366 var elementPath = splitInfo.elementPath;\r
367 if ( elementPath ) {\r
368 for ( var i = 0, len = elementPath.elements.length; i < len; i++ ) {\r
369 var element = elementPath.elements[ i ];\r
370\r
371 if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )\r
372 break;\r
373\r
374 if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) {\r
375 element = element.clone();\r
376 newBlock.moveChildren( element );\r
377 newBlock.append( element );\r
378 }\r
379 }\r
380 }\r
381\r
382 newBlock.appendBogus();\r
383\r
384 if ( !newBlock.getParent() )\r
385 range.insertNode( newBlock );\r
386\r
387 // list item start number should not be duplicated (#7330), but we need\r
388 // to remove the attribute after it's onto the DOM tree because of old IEs (#7581).\r
389 newBlock.is( 'li' ) && newBlock.removeAttribute( 'value' );\r
390\r
391 // This is tricky, but to make the new block visible correctly\r
392 // we must select it.\r
393 // The previousBlock check has been included because it may be\r
394 // empty if we have fixed a block-less space (like ENTER into an\r
395 // empty table cell).\r
396 if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) ) {\r
397 // Move the selection to the new block.\r
398 range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );\r
399 range.select();\r
400 }\r
401\r
402 // Move the selection to the new block.\r
403 range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );\r
404 }\r
405\r
406 range.select();\r
407 range.scrollIntoView();\r
408 },\r
409\r
410 enterBr: function( editor, mode, range, forceMode ) {\r
411 // Get the range for the current selection.\r
412 range = range || getRange( editor );\r
413\r
414 // We may not have valid ranges to work on, like when inside a\r
415 // contenteditable=false element.\r
416 if ( !range )\r
417 return;\r
418\r
419 var doc = range.document;\r
420\r
421 var isEndOfBlock = range.checkEndOfBlock();\r
422\r
423 var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );\r
424\r
425 var startBlock = elementPath.block,\r
426 startBlockTag = startBlock && elementPath.block.getName();\r
427\r
428 if ( !forceMode && startBlockTag == 'li' ) {\r
429 enterBlock( editor, mode, range, forceMode );\r
430 return;\r
431 }\r
432\r
433 // If we are at the end of a header block.\r
434 if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) ) {\r
435 var newBlock, newBlockDir;\r
436\r
437 if ( ( newBlockDir = startBlock.getDirection() ) ) {\r
438 newBlock = doc.createElement( 'div' );\r
439 newBlock.setAttribute( 'dir', newBlockDir );\r
440 newBlock.insertAfter( startBlock );\r
441 range.setStart( newBlock, 0 );\r
442 } else {\r
443 // Insert a <br> after the current paragraph.\r
444 doc.createElement( 'br' ).insertAfter( startBlock );\r
445\r
446 // A text node is required by Gecko only to make the cursor blink.\r
447 if ( CKEDITOR.env.gecko )\r
448 doc.createText( '' ).insertAfter( startBlock );\r
449\r
450 // IE has different behaviors regarding position.\r
451 range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );\r
452 }\r
453 } else {\r
454 var lineBreak;\r
455\r
456 // IE<8 prefers text node as line-break inside of <pre> (#4711).\r
457 if ( startBlockTag == 'pre' && CKEDITOR.env.ie && CKEDITOR.env.version < 8 )\r
458 lineBreak = doc.createText( '\r' );\r
459 else\r
460 lineBreak = doc.createElement( 'br' );\r
461\r
462 range.deleteContents();\r
463 range.insertNode( lineBreak );\r
464\r
465 // Old IEs have different behavior regarding position.\r
466 if ( !CKEDITOR.env.needsBrFiller )\r
467 range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );\r
468 else {\r
469 // A text node is required by Gecko only to make the cursor blink.\r
470 // We need some text inside of it, so the bogus <br> is properly\r
471 // created.\r
472 doc.createText( '\ufeff' ).insertAfter( lineBreak );\r
473\r
474 // If we are at the end of a block, we must be sure the bogus node is available in that block.\r
475 if ( isEndOfBlock ) {\r
476 // In most situations we've got an elementPath.block (e.g. <p>), but in a\r
477 // blockless editor or when autoP is false that needs to be a block limit.\r
478 ( startBlock || elementPath.blockLimit ).appendBogus();\r
479 }\r
480\r
481 // Now we can remove the text node contents, so the caret doesn't\r
482 // stop on it.\r
483 lineBreak.getNext().$.nodeValue = '';\r
484\r
485 range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );\r
486\r
487 }\r
488 }\r
489\r
490 // This collapse guarantees the cursor will be blinking.\r
491 range.collapse( true );\r
492\r
493 range.select();\r
494 range.scrollIntoView();\r
495 }\r
496 };\r
497\r
498 var plugin = CKEDITOR.plugins.enterkey,\r
499 enterBr = plugin.enterBr,\r
500 enterBlock = plugin.enterBlock,\r
501 headerTagRegex = /^h[1-6]$/;\r
502\r
503 function shiftEnter( editor ) {\r
504 // On SHIFT+ENTER:\r
505 // 1. We want to enforce the mode to be respected, instead\r
506 // of cloning the current block. (#77)\r
507 return enter( editor, editor.activeShiftEnterMode, 1 );\r
508 }\r
509\r
510 function enter( editor, mode, forceMode ) {\r
511 forceMode = editor.config.forceEnterMode || forceMode;\r
512\r
513 // Only effective within document.\r
514 if ( editor.mode != 'wysiwyg' )\r
515 return;\r
516\r
517 if ( !mode )\r
518 mode = editor.activeEnterMode;\r
519\r
520 // TODO this should be handled by setting editor.activeEnterMode on selection change.\r
521 // Check path block specialities:\r
522 // 1. Cannot be a un-splittable element, e.g. table caption;\r
523 var path = editor.elementPath();\r
524 if ( !path.isContextFor( 'p' ) ) {\r
525 mode = CKEDITOR.ENTER_BR;\r
526 forceMode = 1;\r
527 }\r
528\r
529 editor.fire( 'saveSnapshot' ); // Save undo step.\r
530\r
531 if ( mode == CKEDITOR.ENTER_BR )\r
532 enterBr( editor, mode, null, forceMode );\r
533 else\r
534 enterBlock( editor, mode, null, forceMode );\r
535\r
536 editor.fire( 'saveSnapshot' );\r
537 }\r
538\r
539 function getRange( editor ) {\r
540 // Get the selection ranges.\r
541 var ranges = editor.getSelection().getRanges( true );\r
542\r
543 // Delete the contents of all ranges except the first one.\r
544 for ( var i = ranges.length - 1; i > 0; i-- ) {\r
545 ranges[ i ].deleteContents();\r
546 }\r
547\r
548 // Return the first range.\r
549 return ranges[ 0 ];\r
550 }\r
551\r
552 function replaceRangeWithClosestEditableRoot( range ) {\r
553 var closestEditable = range.startContainer.getAscendant( function( node ) {\r
554 return node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'true';\r
555 }, true );\r
556\r
557 if ( range.root.equals( closestEditable ) ) {\r
558 return range;\r
559 } else {\r
560 var newRange = new CKEDITOR.dom.range( closestEditable );\r
561\r
562 newRange.moveToRange( range );\r
563 return newRange;\r
564 }\r
565 }\r
566} )();\r