]>
git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/plugins/enterkey/plugin.js
e65555feb3059400259d3ab3443d8c4baacf2789
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 CKEDITOR
.plugins
.add( 'enterkey', {
8 init: function( editor
) {
9 editor
.addCommand( 'enter', {
10 modes: { wysiwyg: 1 },
12 exec: function( editor
) {
17 editor
.addCommand( 'shiftEnter', {
18 modes: { wysiwyg: 1 },
20 exec: function( editor
) {
25 editor
.setKeystroke( [
27 [ CKEDITOR
.SHIFT
+ 13, 'shiftEnter' ]
32 var whitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
33 bookmark
= CKEDITOR
.dom
.walker
.bookmark();
35 CKEDITOR
.plugins
.enterkey
= {
36 enterBlock: function( editor
, mode
, range
, forceMode
) {
37 // Get the range for the current selection.
38 range
= range
|| getRange( editor
);
40 // We may not have valid ranges to work on, like when inside a
41 // contenteditable=false element.
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
);
49 var doc
= range
.document
;
51 var atBlockStart
= range
.checkStartOfBlock(),
52 atBlockEnd
= range
.checkEndOfBlock(),
53 path
= editor
.elementPath( range
.startContainer
),
56 // Determine the block element to be used.
57 blockTag
= ( mode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' ),
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();
69 var blockParent
= block
.getParent(),
70 blockGrandParent
= blockParent
.getParent(),
72 firstChild
= !block
.hasPrevious(),
73 lastChild
= !block
.hasNext(),
75 selection
= editor
.getSelection(),
76 bookmarks
= selection
.createBookmarks(),
78 orgDir
= block
.getDirection( 1 ),
79 className
= block
.getAttribute( 'class' ),
80 style
= block
.getAttribute( 'style' ),
81 dirLoose
= blockGrandParent
.getDirection( 1 ) != orgDir
,
83 enterMode
= editor
.enterMode
,
84 needsBlock
= enterMode
!= CKEDITOR
.ENTER_BR
|| dirLoose
|| style
|| className
,
88 if ( blockGrandParent
.is( 'li' ) ) {
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.
98 // <li>x</li> => <li>x</li>
99 // <li>^</li> => </ul>
101 // </li> => <li>^</li>
107 // <li> => <li>^</li>
109 // <li>^</li> => <ul>
110 // <li>x</li> => <li>x</li>
115 if ( firstChild
|| lastChild
) {
117 // If it's only child, we don't want to keep perent ul anymore.
118 if ( firstChild
&& lastChild
) {
119 blockParent
.remove();
122 block
[lastChild
? 'insertAfter' : 'insertBefore']( blockGrandParent
);
124 // If the empty block is neither first nor last child
125 // then split the list and the block as an element
131 // <li> => <li>x</li>
133 // <li>x</li> => </li>
134 // <li>^</li> => <li>^</li>
135 // <li>y</li> => <li>
137 // </li> => <li>y</li>
143 block
.breakParent( blockGrandParent
);
147 else if ( !needsBlock
) {
148 block
.appendBogus( true );
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.
156 // <li>x</li> => <li>x</li>
157 // <li>^</li> => </ul>
163 // <li>^</li> => <ul>
164 // <li>x</li> => <li>x</li>
167 if ( firstChild
|| lastChild
) {
168 while ( ( child
= block
[ firstChild
? 'getFirst' : 'getLast' ]() ) )
169 child
[ firstChild
? 'insertBefore' : 'insertAfter' ]( blockParent
);
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.
177 // <li>x</li> => <li>x</li>
178 // <li>^</li> => </ul>
185 block
.breakParent( blockParent
);
187 while ( ( child
= block
.getLast() ) )
188 child
.insertAfter( blockParent
);
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' );
199 newBlock
.setAttribute( 'dir', orgDir
);
201 style
&& newBlock
.setAttribute( 'style', style
);
202 className
&& newBlock
.setAttribute( 'class', className
);
204 // Move all the child nodes to the new block.
205 block
.moveChildren( newBlock
);
207 // The original path block is not a list item, just copy the block to out side of the list.
209 newBlock
= path
.block
;
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.
218 // <li>x</li> => <li>x</li>
219 // <li>^</li> => </ul>
225 // <li>^</li> => <ul>
226 // <li>x</li> => <li>x</li>
229 if ( firstChild
|| lastChild
)
230 newBlock
[ firstChild
? 'insertBefore' : 'insertAfter' ]( blockParent
);
232 // If the empty block is neither first nor last child
233 // then split the list and put the new block between
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>
245 block
.breakParent( blockParent
);
246 newBlock
.insertAfter( blockParent
);
252 selection
.selectBookmarks( bookmarks
);
257 if ( block
&& block
.getParent().is( 'blockquote' ) ) {
258 block
.breakParent( block
.getParent() );
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();
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();
268 range
.moveToElementEditStart( block
);
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' ) ) {
276 enterBr( editor
, mode
, range
, forceMode
);
282 var splitInfo
= range
.splitBlock( blockTag
);
287 // Get the current blocks.
288 var previousBlock
= splitInfo
.previousBlock
,
289 nextBlock
= splitInfo
.nextBlock
;
291 var isStartOfBlock
= splitInfo
.wasStartOfBlock
,
292 isEndOfBlock
= splitInfo
.wasEndOfBlock
;
296 // If this is a block under a list item, split it as well. (http://dev.ckeditor.com/ticket/1647)
298 node
= nextBlock
.getParent();
299 if ( node
.is( 'li' ) ) {
300 nextBlock
.breakParent( node
);
301 nextBlock
.move( nextBlock
.getNext(), 1 );
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() );
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
) );
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
);
330 // Move the selection to the end block.
332 range
.moveToElementEditStart( nextBlock
);
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();
344 } else if ( nextBlock
) {
345 newBlock
= nextBlock
.clone();
349 // We have already created a new list item. (http://dev.ckeditor.com/ticket/6849)
350 if ( node
&& node
.is( 'li' ) )
353 newBlock
= doc
.createElement( blockTag
);
354 if ( previousBlock
&& ( newBlockDir
= previousBlock
.getDirection() ) )
355 newBlock
.setAttribute( 'dir', newBlockDir
);
358 // Force the enter block unless we're talking of a list item.
359 else if ( forceMode
&& !newBlock
.is( 'li' ) ) {
360 newBlock
.renameNode( blockTag
);
363 // Recreate the inline elements tree, which was available
364 // before hitting enter, so the same styles will be available in
366 var elementPath
= splitInfo
.elementPath
;
368 for ( var i
= 0, len
= elementPath
.elements
.length
; i
< len
; i
++ ) {
369 var element
= elementPath
.elements
[ i
];
371 if ( element
.equals( elementPath
.block
) || element
.equals( elementPath
.blockLimit
) )
374 if ( CKEDITOR
.dtd
.$removeEmpty
[ element
.getName() ] ) {
375 element
= element
.clone();
376 newBlock
.moveChildren( element
);
377 newBlock
.append( element
);
382 newBlock
.appendBogus();
384 if ( !newBlock
.getParent() )
385 range
.insertNode( newBlock
);
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' );
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
);
402 // Move the selection to the new block.
403 range
.moveToElementEditStart( isStartOfBlock
&& !isEndOfBlock
? nextBlock : newBlock
);
407 range
.scrollIntoView();
410 enterBr: function( editor
, mode
, range
, forceMode
) {
411 // Get the range for the current selection.
412 range
= range
|| getRange( editor
);
414 // We may not have valid ranges to work on, like when inside a
415 // contenteditable=false element.
419 var doc
= range
.document
;
421 var isEndOfBlock
= range
.checkEndOfBlock();
423 var elementPath
= new CKEDITOR
.dom
.elementPath( editor
.getSelection().getStartElement() );
425 var startBlock
= elementPath
.block
,
426 startBlockTag
= startBlock
&& elementPath
.block
.getName();
428 if ( !forceMode
&& startBlockTag
== 'li' ) {
429 enterBlock( editor
, mode
, range
, forceMode
);
433 // If we are at the end of a header block.
434 if ( !forceMode
&& isEndOfBlock
&& headerTagRegex
.test( startBlockTag
) ) {
435 var newBlock
, newBlockDir
;
437 if ( ( newBlockDir
= startBlock
.getDirection() ) ) {
438 newBlock
= doc
.createElement( 'div' );
439 newBlock
.setAttribute( 'dir', newBlockDir
);
440 newBlock
.insertAfter( startBlock
);
441 range
.setStart( newBlock
, 0 );
443 // Insert a <br> after the current paragraph.
444 doc
.createElement( 'br' ).insertAfter( startBlock
);
446 // A text node is required by Gecko only to make the cursor blink.
447 if ( CKEDITOR
.env
.gecko
)
448 doc
.createText( '' ).insertAfter( startBlock
);
450 // IE has different behaviors regarding position.
451 range
.setStartAt( startBlock
.getNext(), CKEDITOR
.env
.ie
? CKEDITOR
.POSITION_BEFORE_START : CKEDITOR
.POSITION_AFTER_START
);
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' );
460 lineBreak
= doc
.createElement( 'br' );
462 range
.deleteContents();
463 range
.insertNode( lineBreak
);
465 // Old IEs have different behavior regarding position.
466 if ( !CKEDITOR
.env
.needsBrFiller
)
467 range
.setStartAt( lineBreak
, CKEDITOR
.POSITION_AFTER_END
);
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
472 doc
.createText( '\ufeff' ).insertAfter( lineBreak
);
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();
481 // Now we can remove the text node contents, so the caret doesn't
483 lineBreak
.getNext().$.nodeValue
= '';
485 range
.setStartAt( lineBreak
.getNext(), CKEDITOR
.POSITION_AFTER_START
);
490 // This collapse guarantees the cursor will be blinking.
491 range
.collapse( true );
494 range
.scrollIntoView();
498 var plugin
= CKEDITOR
.plugins
.enterkey
,
499 enterBr
= plugin
.enterBr
,
500 enterBlock
= plugin
.enterBlock
,
501 headerTagRegex
= /^h[1-6]$/;
503 function shiftEnter( editor
) {
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 );
510 function enter( editor
, mode
, forceMode
) {
511 forceMode
= editor
.config
.forceEnterMode
|| forceMode
;
513 // Only effective within document.
514 if ( editor
.mode
!= 'wysiwyg' )
518 mode
= editor
.activeEnterMode
;
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();
525 if ( path
&& !path
.isContextFor( 'p' ) ) {
526 mode
= CKEDITOR
.ENTER_BR
;
530 editor
.fire( 'saveSnapshot' ); // Save undo step.
532 if ( mode
== CKEDITOR
.ENTER_BR
)
533 enterBr( editor
, mode
, null, forceMode
);
535 enterBlock( editor
, mode
, null, forceMode
);
537 editor
.fire( 'saveSnapshot' );
540 function getRange( editor
) {
541 // Get the selection ranges.
542 var ranges
= editor
.getSelection().getRanges( true );
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();
549 // Return the first range.
553 function replaceRangeWithClosestEditableRoot( range
) {
554 var closestEditable
= range
.startContainer
.getAscendant( function( node
) {
555 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.getAttribute( 'contenteditable' ) == 'true';
558 if ( range
.root
.equals( closestEditable
) ) {
561 var newRange
= new CKEDITOR
.dom
.range( closestEditable
);
563 newRange
.moveToRange( range
);