ForceBlocks.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. /**
  2. * ForceBlocks.js
  3. *
  4. * Copyright 2009, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://tinymce.moxiecode.com/license
  8. * Contributing: http://tinymce.moxiecode.com/contributing
  9. */
  10. (function(tinymce) {
  11. // Shorten names
  12. var Event = tinymce.dom.Event,
  13. isIE = tinymce.isIE,
  14. isGecko = tinymce.isGecko,
  15. isOpera = tinymce.isOpera,
  16. each = tinymce.each,
  17. extend = tinymce.extend,
  18. TRUE = true,
  19. FALSE = false;
  20. function cloneFormats(node) {
  21. var clone, temp, inner;
  22. do {
  23. if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
  24. if (clone) {
  25. temp = node.cloneNode(false);
  26. temp.appendChild(clone);
  27. clone = temp;
  28. } else {
  29. clone = inner = node.cloneNode(false);
  30. }
  31. clone.removeAttribute('id');
  32. }
  33. } while (node = node.parentNode);
  34. if (clone)
  35. return {wrapper : clone, inner : inner};
  36. };
  37. // Checks if the selection/caret is at the end of the specified block element
  38. function isAtEnd(rng, par) {
  39. var rng2 = par.ownerDocument.createRange();
  40. rng2.setStart(rng.endContainer, rng.endOffset);
  41. rng2.setEndAfter(par);
  42. // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
  43. return rng2.cloneContents().textContent.length == 0;
  44. };
  45. function splitList(selection, dom, li) {
  46. var listBlock, block;
  47. if (dom.isEmpty(li)) {
  48. listBlock = dom.getParent(li, 'ul,ol');
  49. if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
  50. dom.split(listBlock, li);
  51. block = dom.create('p', 0, '<br data-mce-bogus="1" />');
  52. dom.replace(block, li);
  53. selection.select(block, 1);
  54. }
  55. return FALSE;
  56. }
  57. return TRUE;
  58. };
  59. /**
  60. * This is a internal class and no method in this class should be called directly form the out side.
  61. */
  62. tinymce.create('tinymce.ForceBlocks', {
  63. ForceBlocks : function(ed) {
  64. var t = this, s = ed.settings, elm;
  65. t.editor = ed;
  66. t.dom = ed.dom;
  67. elm = (s.forced_root_block || 'p').toLowerCase();
  68. s.element = elm.toUpperCase();
  69. ed.onPreInit.add(t.setup, t);
  70. },
  71. setup : function() {
  72. var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
  73. // Force root blocks
  74. if (s.forced_root_block) {
  75. function addRootBlocks() {
  76. var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
  77. if (!node || node.nodeType !== 1)
  78. return;
  79. // Check if node is wrapped in block
  80. while (node != rootNode) {
  81. if (blockElements[node.nodeName])
  82. return;
  83. node = node.parentNode;
  84. }
  85. // Get current selection
  86. rng = selection.getRng();
  87. if (rng.setStart) {
  88. startContainer = rng.startContainer;
  89. startOffset = rng.startOffset;
  90. endContainer = rng.endContainer;
  91. endOffset = rng.endOffset;
  92. } else {
  93. // Force control range into text range
  94. if (rng.item) {
  95. rng = ed.getDoc().body.createTextRange();
  96. rng.moveToElementText(rng.item(0));
  97. }
  98. tmpRng = rng.duplicate();
  99. tmpRng.collapse(true);
  100. startOffset = tmpRng.move('character', offset) * -1;
  101. if (!tmpRng.collapsed) {
  102. tmpRng = rng.duplicate();
  103. tmpRng.collapse(false);
  104. endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
  105. }
  106. }
  107. // Wrap non block elements and text nodes
  108. for (node = rootNode.firstChild; node; node) {
  109. if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
  110. if (!rootBlockNode) {
  111. rootBlockNode = dom.create(s.forced_root_block);
  112. node.parentNode.insertBefore(rootBlockNode, node);
  113. }
  114. tempNode = node;
  115. node = node.nextSibling;
  116. rootBlockNode.appendChild(tempNode);
  117. } else {
  118. rootBlockNode = null;
  119. node = node.nextSibling;
  120. }
  121. }
  122. if (rng.setStart) {
  123. rng.setStart(startContainer, startOffset);
  124. rng.setEnd(endContainer, endOffset);
  125. selection.setRng(rng);
  126. } else {
  127. try {
  128. rng = ed.getDoc().body.createTextRange();
  129. rng.moveToElementText(rootNode);
  130. rng.collapse(true);
  131. rng.moveStart('character', startOffset);
  132. if (endOffset > 0)
  133. rng.moveEnd('character', endOffset);
  134. rng.select();
  135. } catch (ex) {
  136. // Ignore
  137. }
  138. }
  139. ed.nodeChanged();
  140. };
  141. ed.onKeyUp.add(addRootBlocks);
  142. ed.onClick.add(addRootBlocks);
  143. }
  144. if (s.force_br_newlines) {
  145. // Force IE to produce BRs on enter
  146. if (isIE) {
  147. ed.onKeyPress.add(function(ed, e) {
  148. var n;
  149. if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
  150. selection.setContent('<br id="__" /> ', {format : 'raw'});
  151. n = dom.get('__');
  152. n.removeAttribute('id');
  153. selection.select(n);
  154. selection.collapse();
  155. return Event.cancel(e);
  156. }
  157. });
  158. }
  159. }
  160. if (s.force_p_newlines) {
  161. if (!isIE) {
  162. ed.onKeyPress.add(function(ed, e) {
  163. if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
  164. Event.cancel(e);
  165. });
  166. } else {
  167. // Ungly hack to for IE to preserve the formatting when you press
  168. // enter at the end of a block element with formatted contents
  169. // This logic overrides the browsers default logic with
  170. // custom logic that enables us to control the output
  171. tinymce.addUnload(function() {
  172. t._previousFormats = 0; // Fix IE leak
  173. });
  174. ed.onKeyPress.add(function(ed, e) {
  175. t._previousFormats = 0;
  176. // Clone the current formats, this will later be applied to the new block contents
  177. if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
  178. t._previousFormats = cloneFormats(ed.selection.getStart());
  179. });
  180. ed.onKeyUp.add(function(ed, e) {
  181. // Let IE break the element and the wrap the new caret location in the previous formats
  182. if (e.keyCode == 13 && !e.shiftKey) {
  183. var parent = ed.selection.getStart(), fmt = t._previousFormats;
  184. // Parent is an empty block
  185. if (!parent.hasChildNodes() && fmt) {
  186. parent = dom.getParent(parent, dom.isBlock);
  187. if (parent && parent.nodeName != 'LI') {
  188. parent.innerHTML = '';
  189. if (t._previousFormats) {
  190. parent.appendChild(fmt.wrapper);
  191. fmt.inner.innerHTML = '\uFEFF';
  192. } else
  193. parent.innerHTML = '\uFEFF';
  194. selection.select(parent, 1);
  195. selection.collapse(true);
  196. ed.getDoc().execCommand('Delete', false, null);
  197. t._previousFormats = 0;
  198. }
  199. }
  200. }
  201. });
  202. }
  203. if (isGecko) {
  204. ed.onKeyDown.add(function(ed, e) {
  205. if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
  206. t.backspaceDelete(e, e.keyCode == 8);
  207. });
  208. }
  209. }
  210. // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
  211. if (tinymce.isWebKit) {
  212. function insertBr(ed) {
  213. var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
  214. // Insert BR element
  215. rng.insertNode(br = dom.create('br'));
  216. // Place caret after BR
  217. rng.setStartAfter(br);
  218. rng.setEndAfter(br);
  219. selection.setRng(rng);
  220. // Could not place caret after BR then insert an nbsp entity and move the caret
  221. if (selection.getSel().focusNode == br.previousSibling) {
  222. selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
  223. selection.collapse(TRUE);
  224. }
  225. // Create a temporary DIV after the BR and get the position as it
  226. // seems like getPos() returns 0 for text nodes and BR elements.
  227. dom.insertAfter(div, br);
  228. divYPos = dom.getPos(div).y;
  229. dom.remove(div);
  230. // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
  231. if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
  232. ed.getWin().scrollTo(0, divYPos);
  233. };
  234. ed.onKeyPress.add(function(ed, e) {
  235. if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
  236. insertBr(ed);
  237. Event.cancel(e);
  238. }
  239. });
  240. }
  241. // IE specific fixes
  242. if (isIE) {
  243. // Replaces IE:s auto generated paragraphs with the specified element name
  244. if (s.element != 'P') {
  245. ed.onKeyPress.add(function(ed, e) {
  246. t.lastElm = selection.getNode().nodeName;
  247. });
  248. ed.onKeyUp.add(function(ed, e) {
  249. var bl, n = selection.getNode(), b = ed.getBody();
  250. if (b.childNodes.length === 1 && n.nodeName == 'P') {
  251. n = dom.rename(n, s.element);
  252. selection.select(n);
  253. selection.collapse();
  254. ed.nodeChanged();
  255. } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
  256. bl = dom.getParent(n, 'p');
  257. if (bl) {
  258. dom.rename(bl, s.element);
  259. ed.nodeChanged();
  260. }
  261. }
  262. });
  263. }
  264. }
  265. },
  266. getParentBlock : function(n) {
  267. var d = this.dom;
  268. return d.getParent(n, d.isBlock);
  269. },
  270. insertPara : function(e) {
  271. var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
  272. var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
  273. ed.undoManager.beforeChange();
  274. // If root blocks are forced then use Operas default behavior since it's really good
  275. // Removed due to bug: #1853816
  276. // if (se.forced_root_block && isOpera)
  277. // return TRUE;
  278. // Setup before range
  279. rb = d.createRange();
  280. // If is before the first block element and in body, then move it into first block element
  281. rb.setStart(s.anchorNode, s.anchorOffset);
  282. rb.collapse(TRUE);
  283. // Setup after range
  284. ra = d.createRange();
  285. // If is before the first block element and in body, then move it into first block element
  286. ra.setStart(s.focusNode, s.focusOffset);
  287. ra.collapse(TRUE);
  288. // Setup start/end points
  289. dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
  290. sn = dir ? s.anchorNode : s.focusNode;
  291. so = dir ? s.anchorOffset : s.focusOffset;
  292. en = dir ? s.focusNode : s.anchorNode;
  293. eo = dir ? s.focusOffset : s.anchorOffset;
  294. // If selection is in empty table cell
  295. if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
  296. if (sn.firstChild.nodeName == 'BR')
  297. dom.remove(sn.firstChild); // Remove BR
  298. // Create two new block elements
  299. if (sn.childNodes.length == 0) {
  300. ed.dom.add(sn, se.element, null, '<br />');
  301. aft = ed.dom.add(sn, se.element, null, '<br />');
  302. } else {
  303. n = sn.innerHTML;
  304. sn.innerHTML = '';
  305. ed.dom.add(sn, se.element, null, n);
  306. aft = ed.dom.add(sn, se.element, null, '<br />');
  307. }
  308. // Move caret into the last one
  309. r = d.createRange();
  310. r.selectNodeContents(aft);
  311. r.collapse(1);
  312. ed.selection.setRng(r);
  313. return FALSE;
  314. }
  315. // If the caret is in an invalid location in FF we need to move it into the first block
  316. if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
  317. sn = en = sn.firstChild;
  318. so = eo = 0;
  319. rb = d.createRange();
  320. rb.setStart(sn, 0);
  321. ra = d.createRange();
  322. ra.setStart(en, 0);
  323. }
  324. // If the body is totally empty add a BR element this might happen on webkit
  325. if (!d.body.hasChildNodes()) {
  326. d.body.appendChild(dom.create('br'));
  327. }
  328. // Never use body as start or end node
  329. sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
  330. sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
  331. en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
  332. en = en.nodeName == "BODY" ? en.firstChild : en;
  333. // Get start and end blocks
  334. sb = t.getParentBlock(sn);
  335. eb = t.getParentBlock(en);
  336. bn = sb ? sb.nodeName : se.element; // Get block name to create
  337. // Return inside list use default browser behavior
  338. if (n = t.dom.getParent(sb, 'li,pre')) {
  339. if (n.nodeName == 'LI')
  340. return splitList(ed.selection, t.dom, n);
  341. return TRUE;
  342. }
  343. // If caption or absolute layers then always generate new blocks within
  344. if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
  345. bn = se.element;
  346. sb = null;
  347. }
  348. // If caption or absolute layers then always generate new blocks within
  349. if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
  350. bn = se.element;
  351. eb = null;
  352. }
  353. // Use P instead
  354. if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
  355. bn = se.element;
  356. sb = eb = null;
  357. }
  358. // Setup new before and after blocks
  359. bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
  360. aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
  361. // Remove id from after clone
  362. aft.removeAttribute('id');
  363. // Is header and cursor is at the end, then force paragraph under
  364. if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
  365. aft = ed.dom.create(se.element);
  366. // Find start chop node
  367. n = sc = sn;
  368. do {
  369. if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
  370. break;
  371. sc = n;
  372. } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
  373. // Find end chop node
  374. n = ec = en;
  375. do {
  376. if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
  377. break;
  378. ec = n;
  379. } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
  380. // Place first chop part into before block element
  381. if (sc.nodeName == bn)
  382. rb.setStart(sc, 0);
  383. else
  384. rb.setStartBefore(sc);
  385. rb.setEnd(sn, so);
  386. bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
  387. // Place secnd chop part within new block element
  388. try {
  389. ra.setEndAfter(ec);
  390. } catch(ex) {
  391. //console.debug(s.focusNode, s.focusOffset);
  392. }
  393. ra.setStart(en, eo);
  394. aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
  395. // Create range around everything
  396. r = d.createRange();
  397. if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
  398. r.setStartBefore(sc.parentNode);
  399. } else {
  400. if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
  401. r.setStartBefore(rb.startContainer);
  402. else
  403. r.setStart(rb.startContainer, rb.startOffset);
  404. }
  405. if (!ec.nextSibling && ec.parentNode.nodeName == bn)
  406. r.setEndAfter(ec.parentNode);
  407. else
  408. r.setEnd(ra.endContainer, ra.endOffset);
  409. // Delete and replace it with new block elements
  410. r.deleteContents();
  411. if (isOpera)
  412. ed.getWin().scrollTo(0, vp.y);
  413. // Never wrap blocks in blocks
  414. if (bef.firstChild && bef.firstChild.nodeName == bn)
  415. bef.innerHTML = bef.firstChild.innerHTML;
  416. if (aft.firstChild && aft.firstChild.nodeName == bn)
  417. aft.innerHTML = aft.firstChild.innerHTML;
  418. function appendStyles(e, en) {
  419. var nl = [], nn, n, i;
  420. e.innerHTML = '';
  421. // Make clones of style elements
  422. if (se.keep_styles) {
  423. n = en;
  424. do {
  425. // We only want style specific elements
  426. if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
  427. nn = n.cloneNode(FALSE);
  428. dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
  429. nl.push(nn);
  430. }
  431. } while (n = n.parentNode);
  432. }
  433. // Append style elements to aft
  434. if (nl.length > 0) {
  435. for (i = nl.length - 1, nn = e; i >= 0; i--)
  436. nn = nn.appendChild(nl[i]);
  437. // Padd most inner style element
  438. nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
  439. return nl[0]; // Move caret to most inner element
  440. } else
  441. e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
  442. };
  443. // Padd empty blocks
  444. if (dom.isEmpty(bef))
  445. appendStyles(bef, sn);
  446. // Fill empty afterblook with current style
  447. if (dom.isEmpty(aft))
  448. car = appendStyles(aft, en);
  449. // Opera needs this one backwards for older versions
  450. if (isOpera && parseFloat(opera.version()) < 9.5) {
  451. r.insertNode(bef);
  452. r.insertNode(aft);
  453. } else {
  454. r.insertNode(aft);
  455. r.insertNode(bef);
  456. }
  457. // Normalize
  458. aft.normalize();
  459. bef.normalize();
  460. // Move cursor and scroll into view
  461. ed.selection.select(aft, true);
  462. ed.selection.collapse(true);
  463. // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
  464. y = ed.dom.getPos(aft).y;
  465. //ch = aft.clientHeight;
  466. // Is element within viewport
  467. if (y < vp.y || y + 25 > vp.y + vp.h) {
  468. ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
  469. /*console.debug(
  470. 'Element: y=' + y + ', h=' + ch + ', ' +
  471. 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
  472. );*/
  473. }
  474. ed.undoManager.add();
  475. return FALSE;
  476. },
  477. backspaceDelete : function(e, bs) {
  478. var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
  479. // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
  480. if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
  481. walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
  482. // Walk the dom backwards until we find a text node
  483. for (n = sc.lastChild; n; n = walker.prev()) {
  484. if (n.nodeType == 3) {
  485. r.setStart(n, n.nodeValue.length);
  486. r.collapse(true);
  487. se.setRng(r);
  488. return;
  489. }
  490. }
  491. }
  492. // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
  493. // This workaround removes the element by hand and moves the caret to the previous element
  494. if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
  495. if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
  496. // Find previous block element
  497. n = sc;
  498. while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
  499. if (n) {
  500. if (sc != b.firstChild) {
  501. // Find last text node
  502. w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
  503. while (tn = w.nextNode())
  504. n = tn;
  505. // Place caret at the end of last text node
  506. r = ed.getDoc().createRange();
  507. r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
  508. r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
  509. se.setRng(r);
  510. // Remove the target container
  511. ed.dom.remove(sc);
  512. }
  513. return Event.cancel(e);
  514. }
  515. }
  516. }
  517. }
  518. });
  519. })(tinymce);