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