2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
8 CKEDITOR
.plugins
.add( 'enterkey',
10 requires
: [ 'keystrokes', 'indent' ],
12 init : function( editor
)
14 editor
.addCommand( 'enter', {
15 modes
: { wysiwyg
:1 },
17 exec : function( editor
){ enter( editor
); }
20 editor
.addCommand( 'shiftEnter', {
21 modes
: { wysiwyg
:1 },
23 exec : function( editor
){ shiftEnter( editor
); }
26 var keystrokes
= editor
.keystrokeHandler
.keystrokes
;
27 keystrokes
[ 13 ] = 'enter';
28 keystrokes
[ CKEDITOR
.SHIFT
+ 13 ] = 'shiftEnter';
32 CKEDITOR
.plugins
.enterkey
=
34 enterBlock : function( editor
, mode
, range
, forceMode
)
36 // Get the range for the current selection.
37 range
= range
|| getRange( editor
);
39 // We may not have valid ranges to work on, like when inside a
40 // contenteditable=false element.
44 var doc
= range
.document
;
46 var atBlockStart
= range
.checkStartOfBlock(),
47 atBlockEnd
= range
.checkEndOfBlock(),
48 path
= new CKEDITOR
.dom
.elementPath( range
.startContainer
),
51 // Exit the list when we're inside an empty list item block. (#5376)
52 if ( atBlockStart
&& atBlockEnd
)
54 if ( block
&& ( block
.is( 'li' ) || block
.getParent().is( 'li' ) ) )
56 editor
.execCommand( 'outdent' );
60 // Don't split <pre> if we're in the middle of it, act as shift enter key.
61 else if ( block
&& block
.is( 'pre' ) )
65 enterBr( editor
, mode
, range
, forceMode
);
69 // Don't split caption blocks. (#7944)
70 else if ( block
&& CKEDITOR
.dtd
.$captionBlock
[ block
.getName() ] )
72 enterBr( editor
, mode
, range
, forceMode
);
76 // Determine the block element to be used.
77 var blockTag
= ( mode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' );
80 var splitInfo
= range
.splitBlock( blockTag
);
85 // Get the current blocks.
86 var previousBlock
= splitInfo
.previousBlock
,
87 nextBlock
= splitInfo
.nextBlock
;
89 var isStartOfBlock
= splitInfo
.wasStartOfBlock
,
90 isEndOfBlock
= splitInfo
.wasEndOfBlock
;
94 // If this is a block under a list item, split it as well. (#1647)
97 node
= nextBlock
.getParent();
98 if ( node
.is( 'li' ) )
100 nextBlock
.breakParent( node
);
101 nextBlock
.move( nextBlock
.getNext(), 1 );
104 else if ( previousBlock
&& ( node
= previousBlock
.getParent() ) && node
.is( 'li' ) )
106 previousBlock
.breakParent( node
);
107 node
= previousBlock
.getNext();
108 range
.moveToElementEditStart( node
);
109 previousBlock
.move( previousBlock
.getPrevious() );
112 // If we have both the previous and next blocks, it means that the
113 // boundaries were on separated blocks, or none of them where on the
114 // block limits (start/end).
115 if ( !isStartOfBlock
&& !isEndOfBlock
)
117 // If the next block is an <li> with another list tree as the first
118 // child, we'll need to append a filler (<br>/NBSP) or the list item
119 // wouldn't be editable. (#1420)
120 if ( nextBlock
.is( 'li' )
121 && ( node
= nextBlock
.getFirst( CKEDITOR
.dom
.walker
.invisible( true ) ) )
122 && node
.is
&& node
.is( 'ul', 'ol' ) )
123 ( CKEDITOR
.env
.ie
? doc
.createText( '\xa0' ) : doc
.createElement( 'br' ) ).insertBefore( node
);
125 // Move the selection to the end block.
127 range
.moveToElementEditStart( nextBlock
);
136 // Do not enter this block if it's a header tag, or we are in
137 // a Shift+Enter (#77). Create a new block element instead
138 // (later in the code).
139 if ( previousBlock
.is( 'li' ) ||
140 ! ( headerTagRegex
.test( previousBlock
.getName() ) || previousBlock
.is( 'pre' ) ) )
142 // Otherwise, duplicate the previous block.
143 newBlock
= previousBlock
.clone();
146 else if ( nextBlock
)
147 newBlock
= nextBlock
.clone();
151 // We have already created a new list item. (#6849)
152 if ( node
&& node
.is( 'li' ) )
156 newBlock
= doc
.createElement( blockTag
);
157 if ( previousBlock
&& ( newBlockDir
= previousBlock
.getDirection() ) )
158 newBlock
.setAttribute( 'dir', newBlockDir
);
161 // Force the enter block unless we're talking of a list item.
162 else if ( forceMode
&& !newBlock
.is( 'li' ) )
163 newBlock
.renameNode( blockTag
);
165 // Recreate the inline elements tree, which was available
166 // before hitting enter, so the same styles will be available in
168 var elementPath
= splitInfo
.elementPath
;
171 for ( var i
= 0, len
= elementPath
.elements
.length
; i
< len
; i
++ )
173 var element
= elementPath
.elements
[ i
];
175 if ( element
.equals( elementPath
.block
) || element
.equals( elementPath
.blockLimit
) )
178 if ( CKEDITOR
.dtd
.$removeEmpty
[ element
.getName() ] )
180 element
= element
.clone();
181 newBlock
.moveChildren( element
);
182 newBlock
.append( element
);
187 if ( !CKEDITOR
.env
.ie
)
188 newBlock
.appendBogus();
190 if ( !newBlock
.getParent() )
191 range
.insertNode( newBlock
);
193 // list item start number should not be duplicated (#7330), but we need
194 // to remove the attribute after it's onto the DOM tree because of old IEs (#7581).
195 newBlock
.is( 'li' ) && newBlock
.removeAttribute( 'value' );
197 // This is tricky, but to make the new block visible correctly
198 // we must select it.
199 // The previousBlock check has been included because it may be
200 // empty if we have fixed a block-less space (like ENTER into an
201 // empty table cell).
202 if ( CKEDITOR
.env
.ie
&& isStartOfBlock
&& ( !isEndOfBlock
|| !previousBlock
.getChildCount() ) )
204 // Move the selection to the new block.
205 range
.moveToElementEditStart( isEndOfBlock
? previousBlock
: newBlock
);
209 // Move the selection to the new block.
210 range
.moveToElementEditStart( isStartOfBlock
&& !isEndOfBlock
? nextBlock
: newBlock
);
213 if ( !CKEDITOR
.env
.ie
)
217 // If we have split the block, adds a temporary span at the
218 // range position and scroll relatively to it.
219 var tmpNode
= doc
.createElement( 'span' );
221 // We need some content for Safari.
222 tmpNode
.setHtml( ' ' );
224 range
.insertNode( tmpNode
);
225 tmpNode
.scrollIntoView();
226 range
.deleteContents();
230 // We may use the above scroll logic for the new block case
231 // too, but it gives some weird result with Opera.
232 newBlock
.scrollIntoView();
239 enterBr : function( editor
, mode
, range
, forceMode
)
241 // Get the range for the current selection.
242 range
= range
|| getRange( editor
);
244 // We may not have valid ranges to work on, like when inside a
245 // contenteditable=false element.
249 var doc
= range
.document
;
251 // Determine the block element to be used.
252 var blockTag
= ( mode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' );
254 var isEndOfBlock
= range
.checkEndOfBlock();
256 var elementPath
= new CKEDITOR
.dom
.elementPath( editor
.getSelection().getStartElement() );
258 var startBlock
= elementPath
.block
,
259 startBlockTag
= startBlock
&& elementPath
.block
.getName();
263 if ( !forceMode
&& startBlockTag
== 'li' )
265 enterBlock( editor
, mode
, range
, forceMode
);
269 // If we are at the end of a header block.
270 if ( !forceMode
&& isEndOfBlock
&& headerTagRegex
.test( startBlockTag
) )
275 if ( ( newBlockDir
= startBlock
.getDirection() ) )
277 newBlock
= doc
.createElement( 'div' );
278 newBlock
.setAttribute( 'dir', newBlockDir
);
279 newBlock
.insertAfter( startBlock
);
280 range
.setStart( newBlock
, 0 );
284 // Insert a <br> after the current paragraph.
285 doc
.createElement( 'br' ).insertAfter( startBlock
);
287 // A text node is required by Gecko only to make the cursor blink.
288 if ( CKEDITOR
.env
.gecko
)
289 doc
.createText( '' ).insertAfter( startBlock
);
291 // IE has different behaviors regarding position.
292 range
.setStartAt( startBlock
.getNext(), CKEDITOR
.env
.ie
? CKEDITOR
.POSITION_BEFORE_START
: CKEDITOR
.POSITION_AFTER_START
);
299 isPre
= ( startBlockTag
== 'pre' );
301 // Gecko prefers <br> as line-break inside <pre> (#4711).
302 if ( isPre
&& !CKEDITOR
.env
.gecko
)
303 lineBreak
= doc
.createText( CKEDITOR
.env
.ie
? '\r' : '\n' );
305 lineBreak
= doc
.createElement( 'br' );
307 range
.deleteContents();
308 range
.insertNode( lineBreak
);
310 // IE has different behavior regarding position.
311 if ( CKEDITOR
.env
.ie
)
312 range
.setStartAt( lineBreak
, CKEDITOR
.POSITION_AFTER_END
);
315 // A text node is required by Gecko only to make the cursor blink.
316 // We need some text inside of it, so the bogus <br> is properly
318 doc
.createText( '\ufeff' ).insertAfter( lineBreak
);
320 // If we are at the end of a block, we must be sure the bogus node is available in that block.
322 lineBreak
.getParent().appendBogus();
324 // Now we can remove the text node contents, so the caret doesn't
326 lineBreak
.getNext().$.nodeValue
= '';
328 range
.setStartAt( lineBreak
.getNext(), CKEDITOR
.POSITION_AFTER_START
);
330 // Scroll into view, for non IE.
333 // BR is not positioned in Opera and Webkit.
334 if ( !CKEDITOR
.env
.gecko
)
336 dummy
= doc
.createElement( 'span' );
337 // We need have some contents for Webkit to position it
338 // under parent node. ( #3681)
339 dummy
.setHtml(' ');
342 dummy
= doc
.createElement( 'br' );
344 dummy
.insertBefore( lineBreak
.getNext() );
345 dummy
.scrollIntoView();
350 // This collapse guarantees the cursor will be blinking.
351 range
.collapse( true );
353 range
.select( isPre
);
357 var plugin
= CKEDITOR
.plugins
.enterkey
,
358 enterBr
= plugin
.enterBr
,
359 enterBlock
= plugin
.enterBlock
,
360 headerTagRegex
= /^h[1-6]$/;
362 function shiftEnter( editor
)
364 // Only effective within document.
365 if ( editor
.mode
!= 'wysiwyg' )
369 // 1. We want to enforce the mode to be respected, instead
370 // of cloning the current block. (#77)
371 return enter( editor
, editor
.config
.shiftEnterMode
, 1 );
374 function enter( editor
, mode
, forceMode
)
376 forceMode
= editor
.config
.forceEnterMode
|| forceMode
;
378 // Only effective within document.
379 if ( editor
.mode
!= 'wysiwyg' )
383 mode
= editor
.config
.enterMode
;
385 // Use setTimout so the keys get cancelled immediatelly.
386 setTimeout( function()
388 editor
.fire( 'saveSnapshot' ); // Save undo step.
389 if ( mode
== CKEDITOR
.ENTER_BR
)
390 enterBr( editor
, mode
, null, forceMode
);
392 enterBlock( editor
, mode
, null, forceMode
);
399 function getRange( editor
)
401 // Get the selection ranges.
402 var ranges
= editor
.getSelection().getRanges( true );
404 // Delete the contents of all ranges except the first one.
405 for ( var i
= ranges
.length
- 1 ; i
> 0 ; i
-- )
407 ranges
[ i
].deleteContents();
410 // Return the first range.