1 /*
  2 Copyright (c) 2003-2009, 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' ],
 11
 12 		init : function( editor )
 13 		{
 14 			var specialKeys = editor.specialKeys;
 15 			specialKeys[ 13 ] = enter;
 16 			specialKeys[ CKEDITOR.SHIFT + 13 ] = shiftEnter;
 17 		}
 18 	});
 19
 20 	var forceMode,
 21 		headerTagRegex = /^h[1-6]$/;
 22
 23 	function shiftEnter( editor )
 24 	{
 25 		// On SHIFT+ENTER we want to enforce the mode to be respected, instead
 26 		// of cloning the current block. (#77)
 27 		forceMode = 1;
 28
 29 		return enter( editor, editor.config.shiftEnterMode );
 30 	}
 31
 32 	function enter( editor, mode )
 33 	{
 34 		// Only effective within document.
 35 		if ( editor.mode != 'wysiwyg' )
 36 			return;
 37
 38 		if ( !mode )
 39 			mode = editor.config.enterMode;
 40
 41 		// Use setTimout so the keys get cancelled immediatelly.
 42 		setTimeout( function()
 43 			{
 44 				if ( mode == CKEDITOR.ENTER_BR || editor.getSelection().getStartElement().hasAscendant( 'pre', true ) )
 45 					enterBr( editor, mode );
 46 				else
 47 					enterBlock( editor, mode );
 48
 49 				forceMode = 0;
 50 			}, 0 );
 51
 52 		return true;
 53 	}
 54
 55 	function enterBlock( editor, mode, range )
 56 	{
 57 		// Get the range for the current selection.
 58 		range = range || getRange( editor );
 59
 60 		var doc = range.document;
 61
 62 		// Determine the block element to be used.
 63 		var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
 64
 65 		// Split the range.
 66 		var splitInfo = range.splitBlock( blockTag );
 67
 68 		if ( !splitInfo )
 69 			return;
 70
 71 		// Get the current blocks.
 72 		var previousBlock	= splitInfo.previousBlock,
 73 			nextBlock		= splitInfo.nextBlock;
 74
 75 		var isStartOfBlock	= splitInfo.wasStartOfBlock,
 76 			isEndOfBlock	= splitInfo.wasEndOfBlock;
 77
 78 		var node;
 79
 80 		// If this is a block under a list item, split it as well. (#1647)
 81 		if ( nextBlock )
 82 		{
 83 			node = nextBlock.getParent();
 84 			if ( node.is( 'li' ) )
 85 			{
 86 				nextBlock.breakParent( node );
 87 				nextBlock.move( nextBlock.getNext(), true );
 88 			}
 89 		}
 90 		else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) )
 91 		{
 92 			previousBlock.breakParent( node );
 93 			range.moveToElementEditStart( previousBlock.getNext() );
 94 			previousBlock.move( previousBlock.getPrevious() );
 95 		}
 96
 97 		// If we have both the previous and next blocks, it means that the
 98 		// boundaries were on separated blocks, or none of them where on the
 99 		// block limits (start/end).
100 		if ( !isStartOfBlock && !isEndOfBlock )
101 		{
102 			// If the next block is an <li> with another list tree as the first
103 			// child, we'll need to append a placeholder or the list item
104 			// wouldn't be editable. (#1420)
105 			if ( nextBlock.is( 'li' ) && ( node = nextBlock.getFirst() )
106 					&& node.is && node.is( 'ul', 'ol') )
107 				nextBlock.insertBefore( doc.createText( '\xa0' ), node );
108
109 			// Move the selection to the end block.
110 			if ( nextBlock )
111 				range.moveToElementEditStart( nextBlock );
112 		}
113 		else
114 		{
115 			var newBlock;
116
117 			if ( previousBlock )
118 			{
119 				// Do not enter this block if it's a header tag, or we are in
120 				// a Shift+Enter (#77). Create a new block element instead
121 				// (later in the code).
122 				if ( !forceMode && !headerTagRegex.test( previousBlock.getName() ) )
123 				{
124 					// Otherwise, duplicate the previous block.
125 					newBlock = previousBlock.clone();
126 				}
127 			}
128 			else if ( nextBlock )
129 				newBlock = nextBlock.clone();
130
131 			if ( !newBlock )
132 				newBlock = doc.createElement( blockTag );
133
134 			// Recreate the inline elements tree, which was available
135 			// before hitting enter, so the same styles will be available in
136 			// the new block.
137 			var elementPath = splitInfo.elementPath;
138 			if ( elementPath )
139 			{
140 				for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ )
141 				{
142 					var element = elementPath.elements[ i ];
143
144 					if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
145 						break;
146
147 					if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
148 					{
149 						element = element.clone();
150 						newBlock.moveChildren( element );
151 						newBlock.append( element );
152 					}
153 				}
154 			}
155
156 			if ( !CKEDITOR.env.ie )
157 				newBlock.appendBogus();
158
159 			range.insertNode( newBlock );
160
161 			// This is tricky, but to make the new block visible correctly
162 			// we must select it.
163 			// The previousBlock check has been included because it may be
164 			// empty if we have fixed a block-less space (like ENTER into an
165 			// empty table cell).
166 			if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) )
167 			{
168 				// Move the selection to the new block.
169 				range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
170 				range.select();
171 			}
172
173 			// Move the selection to the new block.
174 			range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
175 		}
176
177 		if ( !CKEDITOR.env.ie )
178 		{
179 			if ( nextBlock )
180 			{
181 				// If we have split the block, adds a temporary span at the
182 				// range position and scroll relatively to it.
183 				var tmpNode = doc.createElement( 'span' );
184
185 				// We need some content for Safari.
186 				tmpNode.setHtml( ' ' );
187
188 				range.insertNode( tmpNode );
189 				tmpNode.scrollIntoView();
190 				range.deleteContents();
191 			}
192 			else
193 			{
194 				// We may use the above scroll logic for the new block case
195 				// too, but it gives some weird result with Opera.
196 				newBlock.scrollIntoView();
197 			}
198 		}
199
200 		range.select();
201 	}
202
203 	function enterBr( editor, mode )
204 	{
205 		// Get the range for the current selection.
206 		var range = getRange( editor ),
207 			doc = range.document;
208
209 		// Determine the block element to be used.
210 		var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
211
212 		var isEndOfBlock = range.checkEndOfBlock();
213
214 		var elementPath = new CKEDITOR.dom.elementPath( range.getBoundaryNodes().startNode );
215
216 		var startBlock = elementPath.block,
217 			startBlockTag = startBlock && elementPath.block.getName();
218
219 		var isPre = false;
220
221 		if ( !forceMode && startBlockTag == 'li' )
222 			return enterBlock( editor, mode, range );
223
224 		// If we are at the end of a header block.
225 		if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) )
226 		{
227 			// Insert a <br> after the current paragraph.
228 			doc.createElement( 'br' ).insertAfter( startBlock );
229
230 			// A text node is required by Gecko only to make the cursor blink.
231 			if ( CKEDITOR.env.gecko )
232 				doc.createText( '' ).insertAfter( startBlock );
233
234 			// IE has different behaviors regarding position.
235 			range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
236 		}
237 		else
238 		{
239 			var lineBreak;
240
241 			isPre = ( startBlockTag == 'pre' );
242
243 			if ( isPre )
244 				lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' );
245 			else
246 				lineBreak = doc.createElement( 'br' );
247
248 			range.insertNode( lineBreak );
249
250 			// A text node is required by Gecko only to make the cursor blink.
251 			if ( CKEDITOR.env.gecko )
252 				doc.createText( '' ).insertAfter( lineBreak );
253
254 			// If we are at the end of a block, we must be sure the bogus node is available in that block.
255 			if ( isEndOfBlock && !CKEDITOR.env.ie )
256 				lineBreak.getParent().appendBogus();
257
258 			// IE has different behavior regarding position.
259 			if ( CKEDITOR.env.ie )
260 				range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
261 			else
262 				range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
263
264 			// Scroll into view, for non IE.
265 			if ( !CKEDITOR.env.ie )
266 			{
267 				var dummy = null;
268
269 				if ( CKEDITOR.env.opera )
270 					dummy = doc.createElement( 'span' );
271 				else
272 					dummy = doc.createElement( 'br' );
273
274 				dummy.insertBefore( lineBreak.getNext() );
275 				dummy.scrollIntoView();
276 				dummy.remove();
277 			}
278 		}
279
280 		// This collapse guarantees the cursor will be blinking.
281 		range.collapse( true );
282
283 		range.select( isPre );
284 	}
285
286 	function getRange( editor )
287 	{
288 		// Get the selection ranges.
289 		var ranges = editor.getSelection().getRanges();
290
291 		// Delete the contents of all ranges except the first one.
292 		for ( var i = ranges.length - 1 ; i > 0 ; i-- )
293 		{
294 			ranges[ i ].deleteContents();
295 		}
296
297 		// Return the first range.
298 		return ranges[ 0 ];
299 	}
300 })();
301