EnterKey.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /**
  2. * EnterKey.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * Contains logic for handling the enter key to split/generate block elements.
  12. */
  13. define("tinymce/EnterKey", [
  14. "tinymce/dom/TreeWalker",
  15. "tinymce/dom/RangeUtils",
  16. "tinymce/Env"
  17. ], function(TreeWalker, RangeUtils, Env) {
  18. var isIE = Env.ie && Env.ie < 11;
  19. return function(editor) {
  20. var dom = editor.dom, selection = editor.selection, settings = editor.settings;
  21. var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
  22. function handleEnterKey(evt) {
  23. var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
  24. newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
  25. // Returns true if the block can be split into two blocks or not
  26. function canSplitBlock(node) {
  27. return node &&
  28. dom.isBlock(node) &&
  29. !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
  30. !/^(fixed|absolute)/i.test(node.style.position) &&
  31. dom.getContentEditable(node) !== "true";
  32. }
  33. // Renders empty block on IE
  34. function renderBlockOnIE(block) {
  35. var oldRng;
  36. if (dom.isBlock(block)) {
  37. oldRng = selection.getRng();
  38. block.appendChild(dom.create('span', null, '\u00a0'));
  39. selection.select(block);
  40. block.lastChild.outerHTML = '';
  41. selection.setRng(oldRng);
  42. }
  43. }
  44. // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
  45. function trimInlineElementsOnLeftSideOfBlock(block) {
  46. var node = block, firstChilds = [], i;
  47. // Find inner most first child ex: <p><i><b>*</b></i></p>
  48. while ((node = node.firstChild)) {
  49. if (dom.isBlock(node)) {
  50. return;
  51. }
  52. if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  53. firstChilds.push(node);
  54. }
  55. }
  56. i = firstChilds.length;
  57. while (i--) {
  58. node = firstChilds[i];
  59. if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
  60. dom.remove(node);
  61. } else {
  62. // Remove <a> </a> see #5381
  63. if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
  64. dom.remove(node);
  65. }
  66. }
  67. }
  68. }
  69. // Moves the caret to a suitable position within the root for example in the first non
  70. // pure whitespace text node or before an image
  71. function moveToCaretPosition(root) {
  72. var walker, node, rng, lastNode = root, tempElm;
  73. function firstNonWhiteSpaceNodeSibling(node) {
  74. while (node) {
  75. if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
  76. return node;
  77. }
  78. node = node.nextSibling;
  79. }
  80. }
  81. // Old IE versions doesn't properly render blocks with br elements in them
  82. // For example <p><br></p> wont be rendered correctly in a contentEditable area
  83. // until you remove the br producing <p></p>
  84. if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
  85. if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
  86. dom.remove(parentBlock.firstChild);
  87. }
  88. }
  89. if (root.nodeName == 'LI') {
  90. var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
  91. if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
  92. root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
  93. }
  94. }
  95. rng = dom.createRng();
  96. if (root.hasChildNodes()) {
  97. walker = new TreeWalker(root, root);
  98. while ((node = walker.current())) {
  99. if (node.nodeType == 3) {
  100. rng.setStart(node, 0);
  101. rng.setEnd(node, 0);
  102. break;
  103. }
  104. if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  105. rng.setStartBefore(node);
  106. rng.setEndBefore(node);
  107. break;
  108. }
  109. lastNode = node;
  110. node = walker.next();
  111. }
  112. if (!node) {
  113. rng.setStart(lastNode, 0);
  114. rng.setEnd(lastNode, 0);
  115. }
  116. } else {
  117. if (root.nodeName == 'BR') {
  118. if (root.nextSibling && dom.isBlock(root.nextSibling)) {
  119. // Trick on older IE versions to render the caret before the BR between two lists
  120. if (!documentMode || documentMode < 9) {
  121. tempElm = dom.create('br');
  122. root.parentNode.insertBefore(tempElm, root);
  123. }
  124. rng.setStartBefore(root);
  125. rng.setEndBefore(root);
  126. } else {
  127. rng.setStartAfter(root);
  128. rng.setEndAfter(root);
  129. }
  130. } else {
  131. rng.setStart(root, 0);
  132. rng.setEnd(root, 0);
  133. }
  134. }
  135. selection.setRng(rng);
  136. // Remove tempElm created for old IE:s
  137. dom.remove(tempElm);
  138. selection.scrollIntoView(root);
  139. }
  140. function setForcedBlockAttrs(node) {
  141. var forcedRootBlockName = settings.forced_root_block;
  142. if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
  143. dom.setAttribs(node, settings.forced_root_block_attrs);
  144. }
  145. }
  146. // Creates a new block element by cloning the current one or creating a new one if the name is specified
  147. // This function will also copy any text formatting from the parent block and add it to the new one
  148. function createNewBlock(name) {
  149. var node = container, block, clonedNode, caretNode;
  150. if (name || parentBlockName == "TABLE") {
  151. block = dom.create(name || newBlockName);
  152. setForcedBlockAttrs(block);
  153. } else {
  154. block = parentBlock.cloneNode(false);
  155. }
  156. caretNode = block;
  157. // Clone any parent styles
  158. if (settings.keep_styles !== false) {
  159. do {
  160. if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
  161. // Never clone a caret containers
  162. if (node.id == '_mce_caret') {
  163. continue;
  164. }
  165. clonedNode = node.cloneNode(false);
  166. dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
  167. if (block.hasChildNodes()) {
  168. clonedNode.appendChild(block.firstChild);
  169. block.appendChild(clonedNode);
  170. } else {
  171. caretNode = clonedNode;
  172. block.appendChild(clonedNode);
  173. }
  174. }
  175. } while ((node = node.parentNode));
  176. }
  177. // BR is needed in empty blocks on non IE browsers
  178. if (!isIE) {
  179. caretNode.innerHTML = '<br data-mce-bogus="1">';
  180. }
  181. return block;
  182. }
  183. // Returns true/false if the caret is at the start/end of the parent block element
  184. function isCaretAtStartOrEndOfBlock(start) {
  185. var walker, node, name;
  186. // Caret is in the middle of a text node like "a|b"
  187. if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
  188. return false;
  189. }
  190. // If after the last element in block node edge case for #5091
  191. if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
  192. return true;
  193. }
  194. // If the caret if before the first element in parentBlock
  195. if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
  196. return true;
  197. }
  198. // Caret can be before/after a table
  199. if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
  200. return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
  201. }
  202. // Walk the DOM and look for text nodes or non empty elements
  203. walker = new TreeWalker(container, parentBlock);
  204. // If caret is in beginning or end of a text block then jump to the next/previous node
  205. if (container.nodeType == 3) {
  206. if (start && offset === 0) {
  207. walker.prev();
  208. } else if (!start && offset == container.nodeValue.length) {
  209. walker.next();
  210. }
  211. }
  212. while ((node = walker.current())) {
  213. if (node.nodeType === 1) {
  214. // Ignore bogus elements
  215. if (!node.getAttribute('data-mce-bogus')) {
  216. // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
  217. name = node.nodeName.toLowerCase();
  218. if (nonEmptyElementsMap[name] && name !== 'br') {
  219. return false;
  220. }
  221. }
  222. } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
  223. return false;
  224. }
  225. if (start) {
  226. walker.prev();
  227. } else {
  228. walker.next();
  229. }
  230. }
  231. return true;
  232. }
  233. // Wraps any text nodes or inline elements in the specified forced root block name
  234. function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
  235. var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
  236. // Not in a block element or in a table cell or caption
  237. parentBlock = dom.getParent(container, dom.isBlock);
  238. rootBlockName = editor.getBody().nodeName.toLowerCase();
  239. if (!parentBlock || !canSplitBlock(parentBlock)) {
  240. parentBlock = parentBlock || editableRoot;
  241. if (!parentBlock.hasChildNodes()) {
  242. newBlock = dom.create(blockName);
  243. setForcedBlockAttrs(newBlock);
  244. parentBlock.appendChild(newBlock);
  245. rng.setStart(newBlock, 0);
  246. rng.setEnd(newBlock, 0);
  247. return newBlock;
  248. }
  249. // Find parent that is the first child of parentBlock
  250. node = container;
  251. while (node.parentNode != parentBlock) {
  252. node = node.parentNode;
  253. }
  254. // Loop left to find start node start wrapping at
  255. while (node && !dom.isBlock(node)) {
  256. startNode = node;
  257. node = node.previousSibling;
  258. }
  259. if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
  260. newBlock = dom.create(blockName);
  261. setForcedBlockAttrs(newBlock);
  262. startNode.parentNode.insertBefore(newBlock, startNode);
  263. // Start wrapping until we hit a block
  264. node = startNode;
  265. while (node && !dom.isBlock(node)) {
  266. next = node.nextSibling;
  267. newBlock.appendChild(node);
  268. node = next;
  269. }
  270. // Restore range to it's past location
  271. rng.setStart(container, offset);
  272. rng.setEnd(container, offset);
  273. }
  274. }
  275. return container;
  276. }
  277. // Inserts a block or br before/after or in the middle of a split list of the LI is empty
  278. function handleEmptyListItem() {
  279. function isFirstOrLastLi(first) {
  280. var node = containerBlock[first ? 'firstChild' : 'lastChild'];
  281. // Find first/last element since there might be whitespace there
  282. while (node) {
  283. if (node.nodeType == 1) {
  284. break;
  285. }
  286. node = node[first ? 'nextSibling' : 'previousSibling'];
  287. }
  288. return node === parentBlock;
  289. }
  290. function getContainerBlock() {
  291. var containerBlockParent = containerBlock.parentNode;
  292. if (containerBlockParent.nodeName == 'LI') {
  293. return containerBlockParent;
  294. }
  295. return containerBlock;
  296. }
  297. // Check if we are in an nested list
  298. var containerBlockParentName = containerBlock.parentNode.nodeName;
  299. if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
  300. newBlockName = 'LI';
  301. }
  302. newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
  303. if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
  304. if (containerBlockParentName == 'LI') {
  305. // Nested list is inside a LI
  306. dom.insertAfter(newBlock, getContainerBlock());
  307. } else {
  308. // Is first and last list item then replace the OL/UL with a text block
  309. dom.replace(newBlock, containerBlock);
  310. }
  311. } else if (isFirstOrLastLi(true)) {
  312. if (containerBlockParentName == 'LI') {
  313. // List nested in an LI then move the list to a new sibling LI
  314. dom.insertAfter(newBlock, getContainerBlock());
  315. newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
  316. newBlock.appendChild(containerBlock);
  317. } else {
  318. // First LI in list then remove LI and add text block before list
  319. containerBlock.parentNode.insertBefore(newBlock, containerBlock);
  320. }
  321. } else if (isFirstOrLastLi()) {
  322. // Last LI in list then remove LI and add text block after list
  323. dom.insertAfter(newBlock, getContainerBlock());
  324. renderBlockOnIE(newBlock);
  325. } else {
  326. // Middle LI in list the split the list and insert a text block in the middle
  327. // Extract after fragment and insert it after the current block
  328. containerBlock = getContainerBlock();
  329. tmpRng = rng.cloneRange();
  330. tmpRng.setStartAfter(parentBlock);
  331. tmpRng.setEndAfter(containerBlock);
  332. fragment = tmpRng.extractContents();
  333. if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
  334. newBlock = fragment.firstChild;
  335. dom.insertAfter(fragment, containerBlock);
  336. } else {
  337. dom.insertAfter(fragment, containerBlock);
  338. dom.insertAfter(newBlock, containerBlock);
  339. }
  340. }
  341. dom.remove(parentBlock);
  342. moveToCaretPosition(newBlock);
  343. undoManager.add();
  344. }
  345. // Walks the parent block to the right and look for BR elements
  346. function hasRightSideContent() {
  347. var walker = new TreeWalker(container, parentBlock), node;
  348. while ((node = walker.next())) {
  349. if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
  350. return true;
  351. }
  352. }
  353. }
  354. // Inserts a BR element if the forced_root_block option is set to false or empty string
  355. function insertBr() {
  356. var brElm, extraBr, marker;
  357. if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
  358. // Insert extra BR element at the end block elements
  359. if (!isIE && !hasRightSideContent()) {
  360. brElm = dom.create('br');
  361. rng.insertNode(brElm);
  362. rng.setStartAfter(brElm);
  363. rng.setEndAfter(brElm);
  364. extraBr = true;
  365. }
  366. }
  367. brElm = dom.create('br');
  368. rng.insertNode(brElm);
  369. // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
  370. if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
  371. brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
  372. }
  373. // Insert temp marker and scroll to that
  374. marker = dom.create('span', {}, '&nbsp;');
  375. brElm.parentNode.insertBefore(marker, brElm);
  376. selection.scrollIntoView(marker);
  377. dom.remove(marker);
  378. if (!extraBr) {
  379. rng.setStartAfter(brElm);
  380. rng.setEndAfter(brElm);
  381. } else {
  382. rng.setStartBefore(brElm);
  383. rng.setEndBefore(brElm);
  384. }
  385. selection.setRng(rng);
  386. undoManager.add();
  387. }
  388. // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
  389. function trimLeadingLineBreaks(node) {
  390. do {
  391. if (node.nodeType === 3) {
  392. node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
  393. }
  394. node = node.firstChild;
  395. } while (node);
  396. }
  397. function getEditableRoot(node) {
  398. var root = dom.getRoot(), parent, editableRoot;
  399. // Get all parents until we hit a non editable parent or the root
  400. parent = node;
  401. while (parent !== root && dom.getContentEditable(parent) !== "false") {
  402. if (dom.getContentEditable(parent) === "true") {
  403. editableRoot = parent;
  404. }
  405. parent = parent.parentNode;
  406. }
  407. return parent !== root ? editableRoot : root;
  408. }
  409. // Adds a BR at the end of blocks that only contains an IMG or INPUT since
  410. // these might be floated and then they won't expand the block
  411. function addBrToBlockIfNeeded(block) {
  412. var lastChild;
  413. // IE will render the blocks correctly other browsers needs a BR
  414. if (!isIE) {
  415. block.normalize(); // Remove empty text nodes that got left behind by the extract
  416. // Check if the block is empty or contains a floated last child
  417. lastChild = block.lastChild;
  418. if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
  419. dom.add(block, 'br');
  420. }
  421. }
  422. }
  423. rng = selection.getRng(true);
  424. // Event is blocked by some other handler for example the lists plugin
  425. if (evt.isDefaultPrevented()) {
  426. return;
  427. }
  428. // Delete any selected contents
  429. if (!rng.collapsed) {
  430. editor.execCommand('Delete');
  431. return;
  432. }
  433. // Setup range items and newBlockName
  434. new RangeUtils(dom).normalize(rng);
  435. container = rng.startContainer;
  436. offset = rng.startOffset;
  437. newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
  438. newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
  439. documentMode = dom.doc.documentMode;
  440. shiftKey = evt.shiftKey;
  441. // Resolve node index
  442. if (container.nodeType == 1 && container.hasChildNodes()) {
  443. isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  444. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  445. if (isAfterLastNodeInContainer && container.nodeType == 3) {
  446. offset = container.nodeValue.length;
  447. } else {
  448. offset = 0;
  449. }
  450. }
  451. // Get editable root node normaly the body element but sometimes a div or span
  452. editableRoot = getEditableRoot(container);
  453. // If there is no editable root then enter is done inside a contentEditable false element
  454. if (!editableRoot) {
  455. return;
  456. }
  457. undoManager.beforeChange();
  458. // If editable root isn't block nor the root of the editor
  459. if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
  460. if (!newBlockName || shiftKey) {
  461. insertBr();
  462. }
  463. return;
  464. }
  465. // Wrap the current node and it's sibling in a default block if it's needed.
  466. // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
  467. // This won't happen if root blocks are disabled or the shiftKey is pressed
  468. if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
  469. container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
  470. }
  471. // Find parent block and setup empty block paddings
  472. parentBlock = dom.getParent(container, dom.isBlock);
  473. containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  474. // Setup block names
  475. parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  476. containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  477. // Enter inside block contained within a LI then split or insert before/after LI
  478. if (containerBlockName == 'LI' && !evt.ctrlKey) {
  479. parentBlock = containerBlock;
  480. parentBlockName = containerBlockName;
  481. }
  482. // Handle enter in LI
  483. if (parentBlockName == 'LI') {
  484. if (!newBlockName && shiftKey) {
  485. insertBr();
  486. return;
  487. }
  488. // Handle enter inside an empty list item
  489. if (dom.isEmpty(parentBlock)) {
  490. handleEmptyListItem();
  491. return;
  492. }
  493. }
  494. // Don't split PRE tags but insert a BR instead easier when writing code samples etc
  495. if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
  496. if (!shiftKey) {
  497. insertBr();
  498. return;
  499. }
  500. } else {
  501. // If no root block is configured then insert a BR by default or if the shiftKey is pressed
  502. if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
  503. insertBr();
  504. return;
  505. }
  506. }
  507. // If parent block is root then never insert new blocks
  508. if (newBlockName && parentBlock === editor.getBody()) {
  509. return;
  510. }
  511. // Default block name if it's not configured
  512. newBlockName = newBlockName || 'P';
  513. // Insert new block before/after the parent block depending on caret location
  514. if (isCaretAtStartOrEndOfBlock()) {
  515. // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
  516. if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
  517. newBlock = createNewBlock(newBlockName);
  518. } else {
  519. newBlock = createNewBlock();
  520. }
  521. // Split the current container block element if enter is pressed inside an empty inner block element
  522. if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
  523. // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
  524. newBlock = dom.split(containerBlock, parentBlock);
  525. } else {
  526. dom.insertAfter(newBlock, parentBlock);
  527. }
  528. moveToCaretPosition(newBlock);
  529. } else if (isCaretAtStartOrEndOfBlock(true)) {
  530. // Insert new block before
  531. newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
  532. renderBlockOnIE(newBlock);
  533. moveToCaretPosition(parentBlock);
  534. } else {
  535. // Extract after fragment and insert it after the current block
  536. tmpRng = rng.cloneRange();
  537. tmpRng.setEndAfter(parentBlock);
  538. fragment = tmpRng.extractContents();
  539. trimLeadingLineBreaks(fragment);
  540. newBlock = fragment.firstChild;
  541. dom.insertAfter(fragment, parentBlock);
  542. trimInlineElementsOnLeftSideOfBlock(newBlock);
  543. addBrToBlockIfNeeded(parentBlock);
  544. moveToCaretPosition(newBlock);
  545. }
  546. dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
  547. // Allow custom handling of new blocks
  548. editor.fire('NewBlock', { newBlock: newBlock });
  549. undoManager.add();
  550. }
  551. editor.on('keydown', function(evt) {
  552. if (evt.keyCode == 13) {
  553. if (handleEnterKey(evt) !== false) {
  554. evt.preventDefault();
  555. }
  556. }
  557. });
  558. };
  559. });