tinymce-rails 3.4.7.0.2 → 3.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,9 @@
5
5
  var tinymce = {
6
6
  majorVersion : '3',
7
7
 
8
- minorVersion : '4.7',
8
+ minorVersion : '4.8',
9
9
 
10
- releaseDate : '2011-11-03',
10
+ releaseDate : '2012-02-02',
11
11
 
12
12
  _init : function() {
13
13
  var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
@@ -810,7 +810,7 @@ tinymce.create('tinymce.util.Dispatcher', {
810
810
  // And this is also more efficient
811
811
  for (i = 0; i<li.length; i++) {
812
812
  c = li[i];
813
- s = c.cb.apply(c.scope, a);
813
+ s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);
814
814
 
815
815
  if (s === false)
816
816
  break;
@@ -853,7 +853,7 @@ tinymce.create('tinymce.util.Dispatcher', {
853
853
 
854
854
  // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
855
855
  u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
856
- u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
856
+ u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
857
857
  each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
858
858
  var s = u[i];
859
859
 
@@ -1323,7 +1323,10 @@ tinymce.create('static tinymce.util.XHR', {
1323
1323
  TAB: 9,
1324
1324
  SPACEBAR: 32,
1325
1325
  UP: 38,
1326
- DOWN: 40
1326
+ DOWN: 40,
1327
+ modifierPressed: function (e) {
1328
+ return e.shiftKey || e.ctrlKey || e.altKey;
1329
+ }
1327
1330
  }
1328
1331
  })(tinymce);
1329
1332
 
@@ -1337,7 +1340,7 @@ tinymce.create('static tinymce.util.XHR', {
1337
1340
  var rng, blockElm, node, clonedSpan, isDelete;
1338
1341
 
1339
1342
  isDelete = e.keyCode == DELETE;
1340
- if (isDelete || e.keyCode == BACKSPACE) {
1343
+ if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1341
1344
  e.preventDefault();
1342
1345
  rng = selection.getRng();
1343
1346
 
@@ -1454,6 +1457,26 @@ tinymce.create('static tinymce.util.XHR', {
1454
1457
  });
1455
1458
  };
1456
1459
 
1460
+ function removeStylesOnPTagsInheritedFromHeadingTag(ed) {
1461
+ ed.onKeyDown.add(function(ed, event) {
1462
+ function checkInHeadingTag(ed) {
1463
+ var currentNode = ed.selection.getNode();
1464
+ var headingTags = 'h1,h2,h3,h4,h5,h6';
1465
+ return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;
1466
+ }
1467
+
1468
+ if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {
1469
+ setTimeout(function() {
1470
+ var currentNode = ed.selection.getNode();
1471
+ if (ed.dom.is(currentNode, 'p')) {
1472
+ ed.dom.setAttrib(currentNode, 'style', null);
1473
+ // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'
1474
+ ed.execCommand('mceCleanup');
1475
+ }
1476
+ }, 0);
1477
+ }
1478
+ });
1479
+ }
1457
1480
  function selectionChangeNodeChanged(ed) {
1458
1481
  var lastRng, selectionTimer;
1459
1482
 
@@ -1478,7 +1501,7 @@ tinymce.create('static tinymce.util.XHR', {
1478
1501
  function ensureBodyHasRoleApplication(ed) {
1479
1502
  document.body.setAttribute("role", "application");
1480
1503
  }
1481
-
1504
+
1482
1505
  tinymce.create('tinymce.util.Quirks', {
1483
1506
  Quirks: function(ed) {
1484
1507
  // WebKit
@@ -1499,6 +1522,7 @@ tinymce.create('static tinymce.util.XHR', {
1499
1522
  removeHrOnBackspace(ed);
1500
1523
  emptyEditorWhenDeleting(ed);
1501
1524
  ensureBodyHasRoleApplication(ed);
1525
+ removeStylesOnPTagsInheritedFromHeadingTag(ed)
1502
1526
  }
1503
1527
 
1504
1528
  // Gecko
@@ -4171,6 +4195,7 @@ tinymce.html.Writer = function(settings) {
4171
4195
 
4172
4196
  return this.run(e, function(e) {
4173
4197
  var s = t.settings;
4198
+ var originalValue = e.getAttribute(n);
4174
4199
  if (v !== null) {
4175
4200
  switch (n) {
4176
4201
  case "style":
@@ -4217,6 +4242,12 @@ tinymce.html.Writer = function(settings) {
4217
4242
  e.setAttribute(n, '' + v, 2);
4218
4243
  else
4219
4244
  e.removeAttribute(n, 2);
4245
+
4246
+ // fire onChangeAttrib event for attributes that have changed
4247
+ if (tinyMCE.activeEditor && originalValue != v) {
4248
+ var ed = tinyMCE.activeEditor;
4249
+ ed.onSetAttrib.dispatch(ed, e, n, v);
4250
+ }
4220
4251
  });
4221
4252
  },
4222
4253
 
@@ -4922,6 +4953,12 @@ tinymce.html.Writer = function(settings) {
4922
4953
  function trim(node) {
4923
4954
  var i, children = node.childNodes, type = node.nodeType;
4924
4955
 
4956
+ function surroundedBySpans(node) {
4957
+ var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
4958
+ var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
4959
+ return previousIsSpan && nextIsSpan;
4960
+ }
4961
+
4925
4962
  if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4926
4963
  return;
4927
4964
 
@@ -4932,7 +4969,10 @@ tinymce.html.Writer = function(settings) {
4932
4969
  // Keep non whitespace text nodes
4933
4970
  if (type == 3 && node.nodeValue.length > 0) {
4934
4971
  // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
4935
- if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4972
+ // Also keep text nodes with only spaces if surrounded by spans.
4973
+ // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
4974
+ var trimmedLength = tinymce.trim(node.nodeValue).length;
4975
+ if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node))
4936
4976
  return;
4937
4977
  } else if (type == 1) {
4938
4978
  // If the only child is a bookmark then move it up
@@ -4969,9 +5009,9 @@ tinymce.html.Writer = function(settings) {
4969
5009
 
4970
5010
  // Insert middle chunk
4971
5011
  if (re)
4972
- pa.replaceChild(re, e);
4973
- else
4974
- pa.insertBefore(e, pe);
5012
+ pa.replaceChild(re, e);
5013
+ else
5014
+ pa.insertBefore(e, pe);
4975
5015
 
4976
5016
  // Insert after chunk
4977
5017
  pa.insertBefore(trim(aft), pe);
@@ -7201,7 +7241,8 @@ tinymce.html.Writer = function(settings) {
7201
7241
  }
7202
7242
 
7203
7243
  s.addRange(r);
7204
- t.selectedRange = s.getRangeAt(0);
7244
+ // adding range isn't always successful so we need to check range count otherwise an exception can occur
7245
+ t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
7205
7246
  }
7206
7247
  } else {
7207
7248
  // Is W3C Range
@@ -7313,6 +7354,11 @@ tinymce.html.Writer = function(settings) {
7313
7354
  normalize : function() {
7314
7355
  var self = this, rng, normalized;
7315
7356
 
7357
+ // TODO:
7358
+ // Retain selection direction.
7359
+ // Lean left/right on Gecko for inline elements.
7360
+ // Run this on mouse up/key up when the user manually moves the selection
7361
+
7316
7362
  // Normalize only on non IE browsers for now
7317
7363
  if (tinymce.isIE)
7318
7364
  return;
@@ -7347,18 +7393,24 @@ tinymce.html.Writer = function(settings) {
7347
7393
  if (node.nodeType === 3) {
7348
7394
  offset = start ? 0 : node.nodeValue.length - 1;
7349
7395
  container = node;
7396
+ normalized = true;
7350
7397
  break;
7351
7398
  }
7352
7399
 
7353
- // Found a BR element that we can place the caret before
7354
- if (node.nodeName === 'BR') {
7400
+ // Found a BR/IMG element that we can place the caret before
7401
+ if (/^(BR|IMG)$/.test(node.nodeName)) {
7355
7402
  offset = dom.nodeIndex(node);
7356
7403
  container = node.parentNode;
7404
+
7405
+ // Put caret after image when moving the end point
7406
+ if (node.nodeName == "IMG" && !start) {
7407
+ offset++;
7408
+ }
7409
+
7410
+ normalized = true;
7357
7411
  break;
7358
7412
  }
7359
7413
  } while (node = (start ? walker.next() : walker.prev()));
7360
-
7361
- normalized = true;
7362
7414
  }
7363
7415
  }
7364
7416
  }
@@ -7373,7 +7425,7 @@ tinymce.html.Writer = function(settings) {
7373
7425
  // Normalize the end points
7374
7426
  normalizeEndPoint(true);
7375
7427
 
7376
- if (rng.collapsed)
7428
+ if (!rng.collapsed)
7377
7429
  normalizeEndPoint();
7378
7430
 
7379
7431
  // Set the selection if it was normalized
@@ -7485,12 +7537,11 @@ tinymce.html.Writer = function(settings) {
7485
7537
  if (!settings.apply_source_formatting)
7486
7538
  settings.indent = false;
7487
7539
 
7488
- settings.remove_trailing_brs = true;
7489
-
7490
7540
  // Default DOM and Schema if they are undefined
7491
7541
  dom = dom || tinymce.DOM;
7492
7542
  schema = schema || new tinymce.html.Schema(settings);
7493
7543
  settings.entity_encoding = settings.entity_encoding || 'named';
7544
+ settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
7494
7545
 
7495
7546
  onPreProcess = new tinymce.util.Dispatcher(self);
7496
7547
 
@@ -7554,8 +7605,8 @@ tinymce.html.Writer = function(settings) {
7554
7605
  function trim(value) {
7555
7606
  return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7556
7607
  .replace(/^[\r\n]*|[\r\n]*$/g, '')
7557
- .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7558
- .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7608
+ .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
7609
+ .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
7559
7610
  };
7560
7611
 
7561
7612
  while (i--) {
@@ -8856,7 +8907,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8856
8907
  // Internal functions
8857
8908
  _setupKeyboardNav : function(){
8858
8909
  var contextMenu, menuItems, t=this;
8859
- contextMenu = DOM.select('#menu_' + t.id)[0];
8910
+ contextMenu = DOM.get('menu_' + t.id);
8860
8911
  menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
8861
8912
  menuItems.splice(0,0,contextMenu);
8862
8913
  t.keyboardNav = new tinymce.ui.KeyboardNavigation({
@@ -8963,10 +9014,26 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8963
9014
  },
8964
9015
 
8965
9016
  postRender : function() {
8966
- var t = this, s = t.settings;
9017
+ var t = this, s = t.settings, bookmark;
8967
9018
 
9019
+ // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
9020
+ // need to keep the selection in case the selection is lost
9021
+ if (tinymce.isIE && t.editor) {
9022
+ tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
9023
+ bookmark = t.editor.selection.getBookmark();
9024
+ });
9025
+ }
8968
9026
  tinymce.dom.Event.add(t.id, 'click', function(e) {
8969
- if (!t.isDisabled())
9027
+ if (!t.isDisabled()) {
9028
+ // restore the selection in case the selection is lost in IE
9029
+ if (tinymce.isIE && t.editor && bookmark) {
9030
+ tinymce.activeEditor.selection.moveToBookmark(bookmark);
9031
+ }
9032
+ return s.onclick.call(s.scope, e);
9033
+ }
9034
+ });
9035
+ tinymce.dom.Event.add(t.id, 'keyup', function(e) {
9036
+ if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
8970
9037
  return s.onclick.call(s.scope, e);
8971
9038
  });
8972
9039
  }
@@ -9673,15 +9740,21 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9673
9740
  }
9674
9741
 
9675
9742
  n = DOM.add(tr, 'td');
9676
- n = DOM.add(n, 'a', {
9677
- role : 'option',
9743
+ var settings = {
9678
9744
  href : 'javascript:;',
9679
9745
  style : {
9680
9746
  backgroundColor : '#' + c
9681
9747
  },
9682
9748
  'title': t.editor.getLang('colors.' + c, c),
9683
9749
  'data-mce-color' : '#' + c
9684
- });
9750
+ };
9751
+
9752
+ // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
9753
+ if (!tinymce.isIE ) {
9754
+ settings['role']= 'option';
9755
+ }
9756
+
9757
+ n = DOM.add(n, 'a', settings);
9685
9758
 
9686
9759
  if (t.editor.forcedHighContrastMode) {
9687
9760
  n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
@@ -10322,7 +10395,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10322
10395
  Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10323
10396
  isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10324
10397
  ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10325
- inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10398
+ inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;
10326
10399
 
10327
10400
  tinymce.create('tinymce.Editor', {
10328
10401
  Editor : function(id, s) {
@@ -10346,6 +10419,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10346
10419
 
10347
10420
  'onPostRender',
10348
10421
 
10422
+ 'onLoad',
10423
+
10349
10424
  'onInit',
10350
10425
 
10351
10426
  'onRemove',
@@ -10408,7 +10483,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10408
10483
 
10409
10484
  'onVisualAid',
10410
10485
 
10411
- 'onSetProgressState'
10486
+ 'onSetProgressState',
10487
+
10488
+ 'onSetAttrib'
10412
10489
  ], function(e) {
10413
10490
  t[e] = new Dispatcher(t);
10414
10491
  });
@@ -10760,7 +10837,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10760
10837
  bc = bc[t.id] || '';
10761
10838
  }
10762
10839
 
10763
- t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';
10840
+ t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
10764
10841
 
10765
10842
  // Domain relaxing enabled, then set document domain
10766
10843
  if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
@@ -11228,7 +11305,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11228
11305
  }
11229
11306
 
11230
11307
  t._refreshContentEditable();
11231
- selection.normalize();
11232
11308
 
11233
11309
  // Is not content editable
11234
11310
  if (!ce)
@@ -12000,30 +12076,32 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12000
12076
 
12001
12077
  // Add block quote deletion handler
12002
12078
  t.onKeyDown.add(function(ed, e) {
12003
- // Was the BACKSPACE key pressed?
12004
- if (e.keyCode != 8)
12079
+ if (e.keyCode != VK.BACKSPACE)
12080
+ return;
12081
+
12082
+ var rng = ed.selection.getRng();
12083
+ if (!rng.collapsed)
12005
12084
  return;
12006
12085
 
12007
- var n = ed.selection.getRng().startContainer;
12008
- var offset = ed.selection.getRng().startOffset;
12086
+ var n = rng.startContainer;
12087
+ var offset = rng.startOffset;
12009
12088
 
12010
12089
  while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
12011
12090
  n = n.parentNode;
12012
-
12091
+
12013
12092
  // Is the cursor at the beginning of a blockquote?
12014
12093
  if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
12015
12094
  // Remove the blockquote
12016
12095
  ed.formatter.toggle('blockquote', null, n.parentNode);
12017
12096
 
12018
12097
  // Move the caret to the beginning of n
12019
- var rng = ed.selection.getRng();
12020
12098
  rng.setStart(n, 0);
12021
12099
  rng.setEnd(n, 0);
12022
12100
  ed.selection.setRng(rng);
12023
12101
  ed.selection.collapse(false);
12024
12102
  }
12025
12103
  });
12026
-
12104
+
12027
12105
 
12028
12106
 
12029
12107
  // Add reset handler
@@ -12231,9 +12309,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12231
12309
  if (target !== t.getBody()) {
12232
12310
  t.dom.setAttrib(target, "style", null);
12233
12311
 
12234
- each(template, function(attr) {
12235
- target.setAttributeNode(attr.cloneNode(true));
12236
- });
12312
+ each(template, function(attr) {
12313
+ target.setAttributeNode(attr.cloneNode(true));
12314
+ });
12237
12315
  }
12238
12316
  };
12239
12317
  }
@@ -12545,6 +12623,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12545
12623
  var parser, serializer, parentNode, rootNode, fragment, args,
12546
12624
  marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
12547
12625
 
12626
+ //selection.normalize();
12627
+
12548
12628
  // Setup parser and serializer
12549
12629
  parser = editor.parser;
12550
12630
  serializer = new tinymce.html.Serializer({}, editor.schema);
@@ -12771,7 +12851,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12771
12851
  addCommands({
12772
12852
  // Override justify commands
12773
12853
  'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12774
- return isFormatMatch('align' + command.substring(7));
12854
+ var name = 'align' + command.substring(7);
12855
+ // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
12856
+ // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
12857
+ var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
12858
+ var matches = tinymce.map(nodes, function(node) {
12859
+ return !!formatter.matchNode(node, name);
12860
+ });
12861
+ return tinymce.inArray(matches, TRUE) !== -1;
12775
12862
  },
12776
12863
 
12777
12864
  'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
@@ -14168,30 +14255,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14168
14255
  function apply(name, vars, node) {
14169
14256
  var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14170
14257
 
14171
- function moveStart(rng) {
14172
- var container = rng.startContainer,
14173
- offset = rng.startOffset,
14174
- walker, node;
14175
-
14176
- // Move startContainer/startOffset in to a suitable node
14177
- if (container.nodeType == 1 || container.nodeValue === "") {
14178
- container = container.nodeType == 1 ? container.childNodes[offset] : container;
14179
-
14180
- // Might fail if the offset is behind the last element in it's container
14181
- if (container) {
14182
- walker = new TreeWalker(container, container.parentNode);
14183
- for (node = walker.current(); node; node = walker.next()) {
14184
- if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14185
- rng.setStart(node, 0);
14186
- break;
14187
- }
14188
- }
14189
- }
14190
- }
14191
-
14192
- return rng;
14193
- };
14194
-
14195
14258
  function setElementFormat(elm, fmt) {
14196
14259
  fmt = fmt || format;
14197
14260
 
@@ -14542,7 +14605,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14542
14605
  }
14543
14606
 
14544
14607
  selection.moveToBookmark(bookmark);
14545
- selection.setRng(moveStart(selection.getRng(TRUE)));
14608
+ moveStart(selection.getRng(TRUE));
14546
14609
  ed.nodeChanged();
14547
14610
  } else
14548
14611
  performCaretAction('apply', name, vars);
@@ -14552,44 +14615,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14552
14615
 
14553
14616
  function remove(name, vars, node) {
14554
14617
  var formatList = get(name), format = formatList[0], bookmark, i, rng;
14555
- function moveStart(rng) {
14556
- var container = rng.startContainer,
14557
- offset = rng.startOffset,
14558
- walker, node, nodes, tmpNode;
14559
-
14560
- // Convert text node into index if possible
14561
- if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14562
- container = container.parentNode;
14563
- offset = nodeIndex(container) + 1;
14564
- }
14565
-
14566
- // Move startContainer/startOffset in to a suitable node
14567
- if (container.nodeType == 1) {
14568
- nodes = container.childNodes;
14569
- container = nodes[Math.min(offset, nodes.length - 1)];
14570
- walker = new TreeWalker(container);
14571
-
14572
- // If offset is at end of the parent node walk to the next one
14573
- if (offset > nodes.length - 1)
14574
- walker.next();
14575
-
14576
- for (node = walker.current(); node; node = walker.next()) {
14577
- if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14578
- // IE has a "neat" feature where it moves the start node into the closest element
14579
- // we can avoid this by inserting an element before it and then remove it after we set the selection
14580
- tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14581
- node.parentNode.insertBefore(tmpNode, node);
14582
-
14583
- // Set selection and remove tmpNode
14584
- rng.setStart(node, 0);
14585
- selection.setRng(rng);
14586
- dom.remove(tmpNode);
14587
-
14588
- return;
14589
- }
14590
- }
14591
- }
14592
- };
14593
14618
 
14594
14619
  // Merges the styles for each node
14595
14620
  function process(node) {
@@ -15677,7 +15702,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15677
15702
  dom.remove(node);
15678
15703
  } else {
15679
15704
  child = findFirstTextNode(node);
15680
- child = child.deleteData(0, 1);
15705
+
15706
+ if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
15707
+ child = child.deleteData(0, 1);
15708
+ }
15709
+
15681
15710
  dom.remove(node, 1);
15682
15711
  }
15683
15712
 
@@ -15807,34 +15836,39 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15807
15836
  }
15808
15837
  };
15809
15838
 
15810
- // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
15811
- ed.onBeforeGetContent.addToTop(function() {
15812
- var nodes = [], i;
15839
+ // Only bind the caret events once
15840
+ if (!self._hasCaretEvents) {
15841
+ // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
15842
+ ed.onBeforeGetContent.addToTop(function() {
15843
+ var nodes = [], i;
15813
15844
 
15814
- if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
15815
- // Mark children
15816
- i = nodes.length;
15817
- while (i--) {
15818
- dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
15845
+ if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
15846
+ // Mark children
15847
+ i = nodes.length;
15848
+ while (i--) {
15849
+ dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
15850
+ }
15819
15851
  }
15820
- }
15821
- });
15852
+ });
15822
15853
 
15823
- // Remove caret container on mouse up and on key up
15824
- tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
15825
- ed[name].addToTop(function() {
15826
- removeCaretContainer();
15854
+ // Remove caret container on mouse up and on key up
15855
+ tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
15856
+ ed[name].addToTop(function() {
15857
+ removeCaretContainer();
15858
+ });
15827
15859
  });
15828
- });
15829
15860
 
15830
- // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
15831
- ed.onKeyDown.addToTop(function(ed, e) {
15832
- var keyCode = e.keyCode;
15861
+ // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
15862
+ ed.onKeyDown.addToTop(function(ed, e) {
15863
+ var keyCode = e.keyCode;
15833
15864
 
15834
- if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
15835
- removeCaretContainer(getParentCaretContainer(selection.getStart()));
15836
- }
15837
- });
15865
+ if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
15866
+ removeCaretContainer(getParentCaretContainer(selection.getStart()));
15867
+ }
15868
+ });
15869
+
15870
+ self._hasCaretEvents = true;
15871
+ }
15838
15872
 
15839
15873
  // Do apply or remove caret format
15840
15874
  if (type == "apply") {
@@ -15843,6 +15877,46 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15843
15877
  removeCaretFormat();
15844
15878
  }
15845
15879
  };
15880
+
15881
+ function moveStart(rng) {
15882
+ var container = rng.startContainer,
15883
+ offset = rng.startOffset,
15884
+ walker, node, nodes, tmpNode;
15885
+
15886
+ // Convert text node into index if possible
15887
+ if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
15888
+ container = container.parentNode;
15889
+ offset = nodeIndex(container) + 1;
15890
+ }
15891
+
15892
+ // Move startContainer/startOffset in to a suitable node
15893
+ if (container.nodeType == 1) {
15894
+ nodes = container.childNodes;
15895
+ container = nodes[Math.min(offset, nodes.length - 1)];
15896
+ walker = new TreeWalker(container);
15897
+
15898
+ // If offset is at end of the parent node walk to the next one
15899
+ if (offset > nodes.length - 1)
15900
+ walker.next();
15901
+
15902
+ for (node = walker.current(); node; node = walker.next()) {
15903
+ if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
15904
+ // IE has a "neat" feature where it moves the start node into the closest element
15905
+ // we can avoid this by inserting an element before it and then remove it after we set the selection
15906
+ tmpNode = dom.create('a', null, INVISIBLE_CHAR);
15907
+ node.parentNode.insertBefore(tmpNode, node);
15908
+
15909
+ // Set selection and remove tmpNode
15910
+ rng.setStart(node, 0);
15911
+ selection.setRng(rng);
15912
+ dom.remove(tmpNode);
15913
+
15914
+ return;
15915
+ }
15916
+ }
15917
+ }
15918
+ };
15919
+
15846
15920
  };
15847
15921
  })(tinymce);
15848
15922