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