]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/ForceBlocks.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / ForceBlocks.js
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
11 (function(tinymce) {
12         // Shorten names
13         var Event = tinymce.dom.Event,
14                 isIE = tinymce.isIE,
15                 isGecko = tinymce.isGecko,
16                 isOpera = tinymce.isOpera,
17                 each = tinymce.each,
18                 extend = tinymce.extend,
19                 TRUE = true,
20                 FALSE = false;
21
22         function cloneFormats(node) {
23                 var clone, temp, inner;
24
25                 do {
26                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
27                                 if (clone) {
28                                         temp = node.cloneNode(false);
29                                         temp.appendChild(clone);
30                                         clone = temp;
31                                 } else {
32                                         clone = inner = node.cloneNode(false);
33                                 }
34
35                                 clone.removeAttribute('id');
36                         }
37                 } while (node = node.parentNode);
38
39                 if (clone)
40                         return {wrapper : clone, inner : inner};
41         };
42
43         // Checks if the selection/caret is at the end of the specified block element
44         function isAtEnd(rng, par) {
45                 var rng2 = par.ownerDocument.createRange();
46
47                 rng2.setStart(rng.endContainer, rng.endOffset);
48                 rng2.setEndAfter(par);
49
50                 // 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
51                 return rng2.cloneContents().textContent.length == 0;
52         };
53
54         function splitList(selection, dom, li) {
55                 var listBlock, block;
56
57                 if (dom.isEmpty(li)) {
58                         listBlock = dom.getParent(li, 'ul,ol');
59
60                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
61                                 dom.split(listBlock, li);
62                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
63                                 dom.replace(block, li);
64                                 selection.select(block, 1);
65                         }
66
67                         return FALSE;
68                 }
69
70                 return TRUE;
71         };
72
73         /**
74          * This is a internal class and no method in this class should be called directly form the out side.
75          */
76         tinymce.create('tinymce.ForceBlocks', {
77                 ForceBlocks : function(ed) {
78                         var t = this, s = ed.settings, elm;
79
80                         t.editor = ed;
81                         t.dom = ed.dom;
82                         elm = (s.forced_root_block || 'p').toLowerCase();
83                         s.element = elm.toUpperCase();
84
85                         ed.onPreInit.add(t.setup, t);
86
87                         if (s.forced_root_block) {
88                                 ed.onInit.add(t.forceRoots, t);
89                                 ed.onSetContent.add(t.forceRoots, t);
90                                 ed.onBeforeGetContent.add(t.forceRoots, t);
91                                 ed.onExecCommand.add(function(ed, cmd) {
92                                         if (cmd == 'mceInsertContent') {
93                                                 t.forceRoots();
94                                                 ed.nodeChanged();
95                                         }
96                                 });
97                         }
98                 },
99
100                 setup : function() {
101                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
102
103                         // Force root blocks when typing and when getting output
104                         if (s.forced_root_block) {
105                                 ed.onBeforeExecCommand.add(t.forceRoots, t);
106                                 ed.onKeyUp.add(t.forceRoots, t);
107                                 ed.onPreProcess.add(t.forceRoots, t);
108                         }
109
110                         if (s.force_br_newlines) {
111                                 // Force IE to produce BRs on enter
112                                 if (isIE) {
113                                         ed.onKeyPress.add(function(ed, e) {
114                                                 var n;
115
116                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
117                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
118                                                         n = dom.get('__');
119                                                         n.removeAttribute('id');
120                                                         selection.select(n);
121                                                         selection.collapse();
122                                                         return Event.cancel(e);
123                                                 }
124                                         });
125                                 }
126                         }
127
128                         if (s.force_p_newlines) {
129                                 if (!isIE) {
130                                         ed.onKeyPress.add(function(ed, e) {
131                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
132                                                         Event.cancel(e);
133                                         });
134                                 } else {
135                                         // Ungly hack to for IE to preserve the formatting when you press
136                                         // enter at the end of a block element with formatted contents
137                                         // This logic overrides the browsers default logic with
138                                         // custom logic that enables us to control the output
139                                         tinymce.addUnload(function() {
140                                                 t._previousFormats = 0; // Fix IE leak
141                                         });
142
143                                         ed.onKeyPress.add(function(ed, e) {
144                                                 t._previousFormats = 0;
145
146                                                 // Clone the current formats, this will later be applied to the new block contents
147                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
148                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
149                                         });
150
151                                         ed.onKeyUp.add(function(ed, e) {
152                                                 // Let IE break the element and the wrap the new caret location in the previous formats
153                                                 if (e.keyCode == 13 && !e.shiftKey) {
154                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
155
156                                                         // Parent is an empty block
157                                                         if (!parent.hasChildNodes() && fmt) {
158                                                                 parent = dom.getParent(parent, dom.isBlock);
159
160                                                                 if (parent && parent.nodeName != 'LI') {
161                                                                         parent.innerHTML = '';
162
163                                                                         if (t._previousFormats) {
164                                                                                 parent.appendChild(fmt.wrapper);
165                                                                                 fmt.inner.innerHTML = '\uFEFF';
166                                                                         } else
167                                                                                 parent.innerHTML = '\uFEFF';
168
169                                                                         selection.select(parent, 1);
170                                                                         selection.collapse(true);
171                                                                         ed.getDoc().execCommand('Delete', false, null);
172                                                                         t._previousFormats = 0;
173                                                                 }
174                                                         }
175                                                 }
176                                         });
177                                 }
178
179                                 if (isGecko) {
180                                         ed.onKeyDown.add(function(ed, e) {
181                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
182                                                         t.backspaceDelete(e, e.keyCode == 8);
183                                         });
184                                 }
185                         }
186
187                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
188                         if (tinymce.isWebKit) {
189                                 function insertBr(ed) {
190                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
191
192                                         // Insert BR element
193                                         rng.insertNode(br = dom.create('br'));
194
195                                         // Place caret after BR
196                                         rng.setStartAfter(br);
197                                         rng.setEndAfter(br);
198                                         selection.setRng(rng);
199
200                                         // Could not place caret after BR then insert an nbsp entity and move the caret
201                                         if (selection.getSel().focusNode == br.previousSibling) {
202                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
203                                                 selection.collapse(TRUE);
204                                         }
205
206                                         // Create a temporary DIV after the BR and get the position as it
207                                         // seems like getPos() returns 0 for text nodes and BR elements.
208                                         dom.insertAfter(div, br);
209                                         divYPos = dom.getPos(div).y;
210                                         dom.remove(div);
211
212                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
213                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
214                                                 ed.getWin().scrollTo(0, divYPos);
215                                 };
216
217                                 ed.onKeyPress.add(function(ed, e) {
218                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
219                                                 insertBr(ed);
220                                                 Event.cancel(e);
221                                         }
222                                 });
223                         }
224
225                         // IE specific fixes
226                         if (isIE) {
227                                 // Replaces IE:s auto generated paragraphs with the specified element name
228                                 if (s.element != 'P') {
229                                         ed.onKeyPress.add(function(ed, e) {
230                                                 t.lastElm = selection.getNode().nodeName;
231                                         });
232
233                                         ed.onKeyUp.add(function(ed, e) {
234                                                 var bl, n = selection.getNode(), b = ed.getBody();
235
236                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
237                                                         n = dom.rename(n, s.element);
238                                                         selection.select(n);
239                                                         selection.collapse();
240                                                         ed.nodeChanged();
241                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
242                                                         bl = dom.getParent(n, 'p');
243
244                                                         if (bl) {
245                                                                 dom.rename(bl, s.element);
246                                                                 ed.nodeChanged();
247                                                         }
248                                                 }
249                                         });
250                                 }
251                         }
252                 },
253
254                 find : function(n, t, s) {
255                         var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
256
257                         while (n = w.nextNode()) {
258                                 c++;
259
260                                 // Index by node
261                                 if (t == 0 && n == s)
262                                         return c;
263
264                                 // Node by index
265                                 if (t == 1 && c == s)
266                                         return n;
267                         }
268
269                         return -1;
270                 },
271
272                 forceRoots : function(ed, e) {
273                         var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
274                         var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
275
276                         // Fix for bug #1863847
277                         //if (e && e.keyCode == 13)
278                         //      return TRUE;
279
280                         // Wrap non blocks into blocks
281                         for (i = nl.length - 1; i >= 0; i--) {
282                                 nx = nl[i];
283
284                                 // Ignore internal elements
285                                 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
286                                         bl = null;
287                                         continue;
288                                 }
289
290                                 // Is text or non block element
291                                 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
292                                         if (!bl) {
293                                                 // Create new block but ignore whitespace
294                                                 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
295                                                         // Store selection
296                                                         if (si == -2 && r) {
297                                                                 if (!isIE || r.setStart) {
298                                                                         // If selection is element then mark it
299                                                                         if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
300                                                                                 // Save the id of the selected element
301                                                                                 eid = n.getAttribute("id");
302                                                                                 n.setAttribute("id", "__mce");
303                                                                         } else {
304                                                                                 // If element is inside body, might not be the case in contentEdiable mode
305                                                                                 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
306                                                                                         so = r.startOffset;
307                                                                                         eo = r.endOffset;
308                                                                                         si = t.find(b, 0, r.startContainer);
309                                                                                         ei = t.find(b, 0, r.endContainer);
310                                                                                 }
311                                                                         }
312                                                                 } else {
313                                                                         // Force control range into text range
314                                                                         if (r.item) {
315                                                                                 tr = d.body.createTextRange();
316                                                                                 tr.moveToElementText(r.item(0));
317                                                                                 r = tr;
318                                                                         }
319
320                                                                         tr = d.body.createTextRange();
321                                                                         tr.moveToElementText(b);
322                                                                         tr.collapse(1);
323                                                                         bp = tr.move('character', c) * -1;
324
325                                                                         tr = r.duplicate();
326                                                                         tr.collapse(1);
327                                                                         sp = tr.move('character', c) * -1;
328
329                                                                         tr = r.duplicate();
330                                                                         tr.collapse(0);
331                                                                         le = (tr.move('character', c) * -1) - sp;
332
333                                                                         si = sp - bp;
334                                                                         ei = le;
335                                                                 }
336                                                         }
337
338                                                         // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
339                                                         // See: http://support.microsoft.com/kb/829907
340                                                         bl = ed.dom.create(ed.settings.forced_root_block);
341                                                         nx.parentNode.replaceChild(bl, nx);
342                                                         bl.appendChild(nx);
343                                                 }
344                                         } else {
345                                                 if (bl.hasChildNodes())
346                                                         bl.insertBefore(nx, bl.firstChild);
347                                                 else
348                                                         bl.appendChild(nx);
349                                         }
350                                 } else
351                                         bl = null; // Time to create new block
352                         }
353
354                         // Restore selection
355                         if (si != -2) {
356                                 if (!isIE || r.setStart) {
357                                         bl = b.getElementsByTagName(ed.settings.element)[0];
358                                         r = d.createRange();
359
360                                         // Select last location or generated block
361                                         if (si != -1)
362                                                 r.setStart(t.find(b, 1, si), so);
363                                         else
364                                                 r.setStart(bl, 0);
365
366                                         // Select last location or generated block
367                                         if (ei != -1)
368                                                 r.setEnd(t.find(b, 1, ei), eo);
369                                         else
370                                                 r.setEnd(bl, 0);
371
372                                         if (s) {
373                                                 s.removeAllRanges();
374                                                 s.addRange(r);
375                                         }
376                                 } else {
377                                         try {
378                                                 r = s.createRange();
379                                                 r.moveToElementText(b);
380                                                 r.collapse(1);
381                                                 r.moveStart('character', si);
382                                                 r.moveEnd('character', ei);
383                                                 r.select();
384                                         } catch (ex) {
385                                                 // Ignore
386                                         }
387                                 }
388                         } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
389                                 // Restore the id of the selected element
390                                 if (eid)
391                                         n.setAttribute('id', eid);
392                                 else
393                                         n.removeAttribute('id');
394
395                                 // Move caret before selected element
396                                 r = d.createRange();
397                                 r.setStartBefore(n);
398                                 r.setEndBefore(n);
399                                 se.setRng(r);
400                         }
401                 },
402
403                 getParentBlock : function(n) {
404                         var d = this.dom;
405
406                         return d.getParent(n, d.isBlock);
407                 },
408
409                 insertPara : function(e) {
410                         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;
411                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
412
413                         ed.undoManager.beforeChange();
414
415                         // If root blocks are forced then use Operas default behavior since it's really good
416 // Removed due to bug: #1853816
417 //                      if (se.forced_root_block && isOpera)
418 //                              return TRUE;
419
420                         // Setup before range
421                         rb = d.createRange();
422
423                         // If is before the first block element and in body, then move it into first block element
424                         rb.setStart(s.anchorNode, s.anchorOffset);
425                         rb.collapse(TRUE);
426
427                         // Setup after range
428                         ra = d.createRange();
429
430                         // If is before the first block element and in body, then move it into first block element
431                         ra.setStart(s.focusNode, s.focusOffset);
432                         ra.collapse(TRUE);
433
434                         // Setup start/end points
435                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
436                         sn = dir ? s.anchorNode : s.focusNode;
437                         so = dir ? s.anchorOffset : s.focusOffset;
438                         en = dir ? s.focusNode : s.anchorNode;
439                         eo = dir ? s.focusOffset : s.anchorOffset;
440
441                         // If selection is in empty table cell
442                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
443                                 if (sn.firstChild.nodeName == 'BR')
444                                         dom.remove(sn.firstChild); // Remove BR
445
446                                 // Create two new block elements
447                                 if (sn.childNodes.length == 0) {
448                                         ed.dom.add(sn, se.element, null, '<br />');
449                                         aft = ed.dom.add(sn, se.element, null, '<br />');
450                                 } else {
451                                         n = sn.innerHTML;
452                                         sn.innerHTML = '';
453                                         ed.dom.add(sn, se.element, null, n);
454                                         aft = ed.dom.add(sn, se.element, null, '<br />');
455                                 }
456
457                                 // Move caret into the last one
458                                 r = d.createRange();
459                                 r.selectNodeContents(aft);
460                                 r.collapse(1);
461                                 ed.selection.setRng(r);
462
463                                 return FALSE;
464                         }
465
466                         // If the caret is in an invalid location in FF we need to move it into the first block
467                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
468                                 sn = en = sn.firstChild;
469                                 so = eo = 0;
470                                 rb = d.createRange();
471                                 rb.setStart(sn, 0);
472                                 ra = d.createRange();
473                                 ra.setStart(en, 0);
474                         }
475
476                         // Never use body as start or end node
477                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
478                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
479                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
480                         en = en.nodeName == "BODY" ? en.firstChild : en;
481
482                         // Get start and end blocks
483                         sb = t.getParentBlock(sn);
484                         eb = t.getParentBlock(en);
485                         bn = sb ? sb.nodeName : se.element; // Get block name to create
486
487                         // Return inside list use default browser behavior
488                         if (n = t.dom.getParent(sb, 'li,pre')) {
489                                 if (n.nodeName == 'LI')
490                                         return splitList(ed.selection, t.dom, n);
491
492                                 return TRUE;
493                         }
494
495                         // If caption or absolute layers then always generate new blocks within
496                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
497                                 bn = se.element;
498                                 sb = null;
499                         }
500
501                         // If caption or absolute layers then always generate new blocks within
502                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
503                                 bn = se.element;
504                                 eb = null;
505                         }
506
507                         // Use P instead
508                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
509                                 bn = se.element;
510                                 sb = eb = null;
511                         }
512
513                         // Setup new before and after blocks
514                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
515                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
516
517                         // Remove id from after clone
518                         aft.removeAttribute('id');
519
520                         // Is header and cursor is at the end, then force paragraph under
521                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
522                                 aft = ed.dom.create(se.element);
523
524                         // Find start chop node
525                         n = sc = sn;
526                         do {
527                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
528                                         break;
529
530                                 sc = n;
531                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
532
533                         // Find end chop node
534                         n = ec = en;
535                         do {
536                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
537                                         break;
538
539                                 ec = n;
540                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
541
542                         // Place first chop part into before block element
543                         if (sc.nodeName == bn)
544                                 rb.setStart(sc, 0);
545                         else
546                                 rb.setStartBefore(sc);
547
548                         rb.setEnd(sn, so);
549                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
550
551                         // Place secnd chop part within new block element
552                         try {
553                                 ra.setEndAfter(ec);
554                         } catch(ex) {
555                                 //console.debug(s.focusNode, s.focusOffset);
556                         }
557
558                         ra.setStart(en, eo);
559                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
560
561                         // Create range around everything
562                         r = d.createRange();
563                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
564                                 r.setStartBefore(sc.parentNode);
565                         } else {
566                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
567                                         r.setStartBefore(rb.startContainer);
568                                 else
569                                         r.setStart(rb.startContainer, rb.startOffset);
570                         }
571
572                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
573                                 r.setEndAfter(ec.parentNode);
574                         else
575                                 r.setEnd(ra.endContainer, ra.endOffset);
576
577                         // Delete and replace it with new block elements
578                         r.deleteContents();
579
580                         if (isOpera)
581                                 ed.getWin().scrollTo(0, vp.y);
582
583                         // Never wrap blocks in blocks
584                         if (bef.firstChild && bef.firstChild.nodeName == bn)
585                                 bef.innerHTML = bef.firstChild.innerHTML;
586
587                         if (aft.firstChild && aft.firstChild.nodeName == bn)
588                                 aft.innerHTML = aft.firstChild.innerHTML;
589
590                         // Padd empty blocks
591                         if (dom.isEmpty(bef))
592                                 bef.innerHTML = '<br />';
593
594                         function appendStyles(e, en) {
595                                 var nl = [], nn, n, i;
596
597                                 e.innerHTML = '';
598
599                                 // Make clones of style elements
600                                 if (se.keep_styles) {
601                                         n = en;
602                                         do {
603                                                 // We only want style specific elements
604                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
605                                                         nn = n.cloneNode(FALSE);
606                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
607                                                         nl.push(nn);
608                                                 }
609                                         } while (n = n.parentNode);
610                                 }
611
612                                 // Append style elements to aft
613                                 if (nl.length > 0) {
614                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
615                                                 nn = nn.appendChild(nl[i]);
616
617                                         // Padd most inner style element
618                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
619                                         return nl[0]; // Move caret to most inner element
620                                 } else
621                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
622                         };
623
624                         // Fill empty afterblook with current style
625                         if (dom.isEmpty(aft))
626                                 car = appendStyles(aft, en);
627
628                         // Opera needs this one backwards for older versions
629                         if (isOpera && parseFloat(opera.version()) < 9.5) {
630                                 r.insertNode(bef);
631                                 r.insertNode(aft);
632                         } else {
633                                 r.insertNode(aft);
634                                 r.insertNode(bef);
635                         }
636
637                         // Normalize
638                         aft.normalize();
639                         bef.normalize();
640
641                         function first(n) {
642                                 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
643                         };
644
645                         // Move cursor and scroll into view
646                         r = d.createRange();
647                         r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
648                         r.collapse(1);
649                         s.removeAllRanges();
650                         s.addRange(r);
651
652                         // 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
653                         y = ed.dom.getPos(aft).y;
654                         //ch = aft.clientHeight;
655
656                         // Is element within viewport
657                         if (y < vp.y || y + 25 > vp.y + vp.h) {
658                                 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
659
660                                 /*console.debug(
661                                         'Element: y=' + y + ', h=' + ch + ', ' +
662                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
663                                 );*/
664                         }
665
666                         ed.undoManager.add();
667
668                         return FALSE;
669                 },
670
671                 backspaceDelete : function(e, bs) {
672                         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;
673
674                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
675                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
676                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
677
678                                 // Walk the dom backwards until we find a text node
679                                 for (n = sc.lastChild; n; n = walker.prev()) {
680                                         if (n.nodeType == 3) {
681                                                 r.setStart(n, n.nodeValue.length);
682                                                 r.collapse(true);
683                                                 se.setRng(r);
684                                                 return;
685                                         }
686                                 }
687                         }
688
689                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
690                         // This workaround removes the element by hand and moves the caret to the previous element
691                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
692                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
693                                         // Find previous block element
694                                         n = sc;
695                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
696
697                                         if (n) {
698                                                 if (sc != b.firstChild) {
699                                                         // Find last text node
700                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
701                                                         while (tn = w.nextNode())
702                                                                 n = tn;
703
704                                                         // Place caret at the end of last text node
705                                                         r = ed.getDoc().createRange();
706                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
707                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
708                                                         se.setRng(r);
709
710                                                         // Remove the target container
711                                                         ed.dom.remove(sc);
712                                                 }
713
714                                                 return Event.cancel(e);
715                                         }
716                                 }
717                         }
718                 }
719         });
720 })(tinymce);