plugin.js 20 KB

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