1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 (function()
  7 {
  8 	CKEDITOR.plugins.add( 'enterkey',
  9 	{
 10 		requires : [ 'keystrokes', 'indent' ],
 11 
 12 		init : function( editor )
 13 		{
 14 			editor.addCommand( 'enter', {
 15 				modes : { wysiwyg:1 },
 16 				editorFocus : false,
 17 				exec : function( editor ){ enter( editor ); }
 18 			});
 19 
 20 			editor.addCommand( 'shiftEnter', {
 21 				modes : { wysiwyg:1 },
 22 				editorFocus : false,
 23 				exec : function( editor ){ shiftEnter( editor ); }
 24 			});
 25 
 26 			var keystrokes = editor.keystrokeHandler.keystrokes;
 27 			keystrokes[ 13 ] = 'enter';
 28 			keystrokes[ CKEDITOR.SHIFT + 13 ] = 'shiftEnter';
 29 		}
 30 	});
 31 
 32 	CKEDITOR.plugins.enterkey =
 33 	{
 34 		enterBlock : function( editor, mode, range, forceMode )
 35 		{
 36 			// Get the range for the current selection.
 37 			range = range || getRange( editor );
 38 
 39 			// We may not have valid ranges to work on, like when inside a
 40 			// contenteditable=false element.
 41 			if ( !range )
 42 				return;
 43 
 44 			var doc = range.document;
 45 
 46 			var atBlockStart = range.checkStartOfBlock(),
 47 				atBlockEnd = range.checkEndOfBlock(),
 48 				path = new CKEDITOR.dom.elementPath( range.startContainer ),
 49 				block = path.block;
 50 
 51 			if ( atBlockStart && atBlockEnd )
 52 			{
 53 				// Exit the list when we're inside an empty list item block. (#5376)
 54 				if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) )
 55 				{
 56 					editor.execCommand( 'outdent' );
 57 					return;
 58 				}
 59 
 60 				if ( block && block.getParent().is( 'blockquote' ) )
 61 				{
 62 					block.breakParent( block.getParent() );
 63 
 64 					// If we were at the start of <blockquote>, there will be an empty element before it now.
 65 					if ( !block.getPrevious().getFirst( CKEDITOR.dom.walker.invisible(1) ) )
 66 						block.getPrevious().remove();
 67 
 68 					// If we were at the end of <blockquote>, there will be an empty element after it now.
 69 					if ( !block.getNext().getFirst( CKEDITOR.dom.walker.invisible(1) ) )
 70 						block.getNext().remove();
 71 
 72 					range.moveToElementEditStart( block );
 73 					range.select();
 74 					return;
 75 				}
 76 			}
 77 			// Don't split <pre> if we're in the middle of it, act as shift enter key.
 78 			else if ( block && block.is( 'pre' ) )
 79 			{
 80 				if ( !atBlockEnd )
 81 				{
 82 					enterBr( editor, mode, range, forceMode );
 83 					return;
 84 				}
 85 			}
 86 			// Don't split caption blocks. (#7944)
 87 			else if ( block && CKEDITOR.dtd.$captionBlock[ block.getName() ] )
 88 			{
 89 				enterBr( editor, mode, range, forceMode );
 90 				return;
 91 			}
 92 
 93 			// Determine the block element to be used.
 94 			var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
 95 
 96 			// Split the range.
 97 			var splitInfo = range.splitBlock( blockTag );
 98 
 99 			if ( !splitInfo )
100 				return;
101 
102 			// Get the current blocks.
103 			var previousBlock	= splitInfo.previousBlock,
104 				nextBlock		= splitInfo.nextBlock;
105 
106 			var isStartOfBlock	= splitInfo.wasStartOfBlock,
107 				isEndOfBlock	= splitInfo.wasEndOfBlock;
108 
109 			var node;
110 
111 			// If this is a block under a list item, split it as well. (#1647)
112 			if ( nextBlock )
113 			{
114 				node = nextBlock.getParent();
115 				if ( node.is( 'li' ) )
116 				{
117 					nextBlock.breakParent( node );
118 					nextBlock.move( nextBlock.getNext(), 1 );
119 				}
120 			}
121 			else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) )
122 			{
123 				previousBlock.breakParent( node );
124 				node = previousBlock.getNext();
125 				range.moveToElementEditStart( node );
126 				previousBlock.move( previousBlock.getPrevious() );
127 			}
128 
129 			// If we have both the previous and next blocks, it means that the
130 			// boundaries were on separated blocks, or none of them where on the
131 			// block limits (start/end).
132 			if ( !isStartOfBlock && !isEndOfBlock )
133 			{
134 				// If the next block is an <li> with another list tree as the first
135 				// child, we'll need to append a filler (<br>/NBSP) or the list item
136 				// wouldn't be editable. (#1420)
137 				if ( nextBlock.is( 'li' )
138 					 && ( node = nextBlock.getFirst( CKEDITOR.dom.walker.invisible( true ) ) )
139 					 && node.is && node.is( 'ul', 'ol' ) )
140 					( CKEDITOR.env.ie ? doc.createText( '\xa0' ) : doc.createElement( 'br' ) ).insertBefore( node );
141 
142 				// Move the selection to the end block.
143 				if ( nextBlock )
144 					range.moveToElementEditStart( nextBlock );
145 			}
146 			else
147 			{
148 				var newBlock,
149 					newBlockDir;
150 
151 				if ( previousBlock )
152 				{
153 					// Do not enter this block if it's a header tag, or we are in
154 					// a Shift+Enter (#77). Create a new block element instead
155 					// (later in the code).
156 					if ( previousBlock.is( 'li' ) ||
157 							! ( headerTagRegex.test( previousBlock.getName() ) || previousBlock.is( 'pre' ) ) )
158 					{
159 						// Otherwise, duplicate the previous block.
160 						newBlock = previousBlock.clone();
161 					}
162 				}
163 				else if ( nextBlock )
164 					newBlock = nextBlock.clone();
165 
166 				if ( !newBlock )
167 				{
168 					// We have already created a new list item. (#6849)
169 					if ( node && node.is( 'li' ) )
170 						newBlock = node;
171 					else
172 					{
173 						newBlock = doc.createElement( blockTag );
174 						if ( previousBlock && ( newBlockDir = previousBlock.getDirection() ) )
175 							newBlock.setAttribute( 'dir', newBlockDir );
176 					}
177 				}
178 				// Force the enter block unless we're talking of a list item.
179 				else if ( forceMode && !newBlock.is( 'li' ) )
180 					newBlock.renameNode( blockTag );
181 
182 				// Recreate the inline elements tree, which was available
183 				// before hitting enter, so the same styles will be available in
184 				// the new block.
185 				var elementPath = splitInfo.elementPath;
186 				if ( elementPath )
187 				{
188 					for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ )
189 					{
190 						var element = elementPath.elements[ i ];
191 
192 						if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
193 							break;
194 
195 						if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
196 						{
197 							element = element.clone();
198 							newBlock.moveChildren( element );
199 							newBlock.append( element );
200 						}
201 					}
202 				}
203 
204 				if ( !CKEDITOR.env.ie )
205 					newBlock.appendBogus();
206 
207 				if ( !newBlock.getParent() )
208 					range.insertNode( newBlock );
209 
210 				// list item start number should not be duplicated (#7330), but we need
211 				// to remove the attribute after it's onto the DOM tree because of old IEs (#7581).
212 				newBlock.is( 'li' ) && newBlock.removeAttribute( 'value' );
213 
214 				// This is tricky, but to make the new block visible correctly
215 				// we must select it.
216 				// The previousBlock check has been included because it may be
217 				// empty if we have fixed a block-less space (like ENTER into an
218 				// empty table cell).
219 				if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) )
220 				{
221 					// Move the selection to the new block.
222 					range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
223 					range.select();
224 				}
225 
226 				// Move the selection to the new block.
227 				range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
228 		}
229 
230 			if ( !CKEDITOR.env.ie )
231 			{
232 				if ( nextBlock )
233 				{
234 					// If we have split the block, adds a temporary span at the
235 					// range position and scroll relatively to it.
236 					var tmpNode = doc.createElement( 'span' );
237 
238 					// We need some content for Safari.
239 					tmpNode.setHtml( ' ' );
240 
241 					range.insertNode( tmpNode );
242 					tmpNode.scrollIntoView();
243 					range.deleteContents();
244 				}
245 				else
246 				{
247 					// We may use the above scroll logic for the new block case
248 					// too, but it gives some weird result with Opera.
249 					newBlock.scrollIntoView();
250 				}
251 			}
252 
253 			range.select();
254 		},
255 
256 		enterBr : function( editor, mode, range, forceMode )
257 		{
258 			// Get the range for the current selection.
259 			range = range || getRange( editor );
260 
261 			// We may not have valid ranges to work on, like when inside a
262 			// contenteditable=false element.
263 			if ( !range )
264 				return;
265 
266 			var doc = range.document;
267 
268 			// Determine the block element to be used.
269 			var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
270 
271 			var isEndOfBlock = range.checkEndOfBlock();
272 
273 			var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );
274 
275 			var startBlock = elementPath.block,
276 				startBlockTag = startBlock && elementPath.block.getName();
277 
278 			var isPre = false;
279 
280 			if ( !forceMode && startBlockTag == 'li' )
281 			{
282 				enterBlock( editor, mode, range, forceMode );
283 				return;
284 			}
285 
286 			// If we are at the end of a header block.
287 			if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) )
288 			{
289 				var newBlock,
290 					newBlockDir;
291 
292 				if ( ( newBlockDir = startBlock.getDirection() ) )
293 				{
294 					newBlock = doc.createElement( 'div' );
295 					newBlock.setAttribute( 'dir', newBlockDir );
296 					newBlock.insertAfter( startBlock );
297 					range.setStart( newBlock, 0 );
298 				}
299 				else
300 				{
301 					// Insert a <br> after the current paragraph.
302 					doc.createElement( 'br' ).insertAfter( startBlock );
303 
304 					// A text node is required by Gecko only to make the cursor blink.
305 					if ( CKEDITOR.env.gecko )
306 						doc.createText( '' ).insertAfter( startBlock );
307 
308 					// IE has different behaviors regarding position.
309 					range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
310 				}
311 			}
312 			else
313 			{
314 				var lineBreak;
315 
316 				isPre = ( startBlockTag == 'pre' );
317 
318 				// Gecko prefers <br> as line-break inside <pre> (#4711).
319 				if ( isPre && !CKEDITOR.env.gecko )
320 					lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' );
321 				else
322 					lineBreak = doc.createElement( 'br' );
323 
324 				range.deleteContents();
325 				range.insertNode( lineBreak );
326 
327 				// IE has different behavior regarding position.
328 				if ( CKEDITOR.env.ie )
329 					range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
330 				else
331 				{
332 					// A text node is required by Gecko only to make the cursor blink.
333 					// We need some text inside of it, so the bogus <br> is properly
334 					// created.
335 					doc.createText( '\ufeff' ).insertAfter( lineBreak );
336 
337 					// If we are at the end of a block, we must be sure the bogus node is available in that block.
338 					if ( isEndOfBlock )
339 						lineBreak.getParent().appendBogus();
340 
341 					// Now we can remove the text node contents, so the caret doesn't
342 					// stop on it.
343 					lineBreak.getNext().$.nodeValue = '';
344 
345 					range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
346 
347 					// Scroll into view, for non IE.
348 					var dummy = null;
349 
350 					// BR is not positioned in Opera and Webkit.
351 					if ( !CKEDITOR.env.gecko )
352 					{
353 						dummy = doc.createElement( 'span' );
354 						// We need have some contents for Webkit to position it
355 						// under parent node. ( #3681)
356 						dummy.setHtml(' ');
357 					}
358 					else
359 						dummy = doc.createElement( 'br' );
360 
361 					dummy.insertBefore( lineBreak.getNext() );
362 					dummy.scrollIntoView();
363 					dummy.remove();
364 				}
365 			}
366 
367 			// This collapse guarantees the cursor will be blinking.
368 			range.collapse( true );
369 
370 			range.select( isPre );
371 		}
372 	};
373 
374 	var plugin = CKEDITOR.plugins.enterkey,
375 		enterBr = plugin.enterBr,
376 		enterBlock = plugin.enterBlock,
377 		headerTagRegex = /^h[1-6]$/;
378 
379 	function shiftEnter( editor )
380 	{
381 		// Only effective within document.
382 		if ( editor.mode != 'wysiwyg' )
383 			return false;
384 
385 		// On SHIFT+ENTER:
386 		// 1. We want to enforce the mode to be respected, instead
387 		// of cloning the current block. (#77)
388 		return enter( editor, editor.config.shiftEnterMode, 1 );
389 	}
390 
391 	function enter( editor, mode, forceMode )
392 	{
393 		forceMode = editor.config.forceEnterMode || forceMode;
394 
395 		// Only effective within document.
396 		if ( editor.mode != 'wysiwyg' )
397 			return false;
398 
399 		if ( !mode )
400 			mode = editor.config.enterMode;
401 
402 		// Use setTimout so the keys get cancelled immediatelly.
403 		setTimeout( function()
404 			{
405 				editor.fire( 'saveSnapshot' );	// Save undo step.
406 
407 				if ( mode == CKEDITOR.ENTER_BR )
408 					enterBr( editor, mode, null, forceMode );
409 				else
410 					enterBlock( editor, mode, null, forceMode );
411 
412 				editor.fire( 'saveSnapshot' );
413 
414 			}, 0 );
415 
416 		return true;
417 	}
418 
419 	function getRange( editor )
420 	{
421 		// Get the selection ranges.
422 		var ranges = editor.getSelection().getRanges( true );
423 
424 		// Delete the contents of all ranges except the first one.
425 		for ( var i = ranges.length - 1 ; i > 0 ; i-- )
426 		{
427 			ranges[ i ].deleteContents();
428 		}
429 
430 		// Return the first range.
431 		return ranges[ 0 ];
432 	}
433 })();
434