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