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;
@@ -535,7 +535,7 @@ tinymce.create('tinymce.util.Dispatcher', {
535
535
  // And this is also more efficient
536
536
  for (i = 0; i<li.length; i++) {
537
537
  c = li[i];
538
- s = c.cb.apply(c.scope, a);
538
+ s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);
539
539
 
540
540
  if (s === false)
541
541
  break;
@@ -578,7 +578,7 @@ tinymce.create('tinymce.util.Dispatcher', {
578
578
 
579
579
  // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
580
580
  u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
581
- u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
581
+ u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
582
582
  each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
583
583
  var s = u[i];
584
584
 
@@ -1048,7 +1048,10 @@ tinymce.create('static tinymce.util.XHR', {
1048
1048
  TAB: 9,
1049
1049
  SPACEBAR: 32,
1050
1050
  UP: 38,
1051
- DOWN: 40
1051
+ DOWN: 40,
1052
+ modifierPressed: function (e) {
1053
+ return e.shiftKey || e.ctrlKey || e.altKey;
1054
+ }
1052
1055
  }
1053
1056
  })(tinymce);
1054
1057
 
@@ -1062,7 +1065,7 @@ tinymce.create('static tinymce.util.XHR', {
1062
1065
  var rng, blockElm, node, clonedSpan, isDelete;
1063
1066
 
1064
1067
  isDelete = e.keyCode == DELETE;
1065
- if (isDelete || e.keyCode == BACKSPACE) {
1068
+ if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1066
1069
  e.preventDefault();
1067
1070
  rng = selection.getRng();
1068
1071
 
@@ -1179,6 +1182,26 @@ tinymce.create('static tinymce.util.XHR', {
1179
1182
  });
1180
1183
  };
1181
1184
 
1185
+ function removeStylesOnPTagsInheritedFromHeadingTag(ed) {
1186
+ ed.onKeyDown.add(function(ed, event) {
1187
+ function checkInHeadingTag(ed) {
1188
+ var currentNode = ed.selection.getNode();
1189
+ var headingTags = 'h1,h2,h3,h4,h5,h6';
1190
+ return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;
1191
+ }
1192
+
1193
+ if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {
1194
+ setTimeout(function() {
1195
+ var currentNode = ed.selection.getNode();
1196
+ if (ed.dom.is(currentNode, 'p')) {
1197
+ ed.dom.setAttrib(currentNode, 'style', null);
1198
+ // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'
1199
+ ed.execCommand('mceCleanup');
1200
+ }
1201
+ }, 0);
1202
+ }
1203
+ });
1204
+ }
1182
1205
  function selectionChangeNodeChanged(ed) {
1183
1206
  var lastRng, selectionTimer;
1184
1207
 
@@ -1203,7 +1226,7 @@ tinymce.create('static tinymce.util.XHR', {
1203
1226
  function ensureBodyHasRoleApplication(ed) {
1204
1227
  document.body.setAttribute("role", "application");
1205
1228
  }
1206
-
1229
+
1207
1230
  tinymce.create('tinymce.util.Quirks', {
1208
1231
  Quirks: function(ed) {
1209
1232
  // WebKit
@@ -1224,6 +1247,7 @@ tinymce.create('static tinymce.util.XHR', {
1224
1247
  removeHrOnBackspace(ed);
1225
1248
  emptyEditorWhenDeleting(ed);
1226
1249
  ensureBodyHasRoleApplication(ed);
1250
+ removeStylesOnPTagsInheritedFromHeadingTag(ed)
1227
1251
  }
1228
1252
 
1229
1253
  // Gecko
@@ -3929,6 +3953,7 @@ tinymce.html.Writer = function(settings) {
3929
3953
 
3930
3954
  return this.run(e, function(e) {
3931
3955
  var s = t.settings;
3956
+ var originalValue = e.getAttribute(n);
3932
3957
  if (v !== null) {
3933
3958
  switch (n) {
3934
3959
  case "style":
@@ -3975,6 +4000,12 @@ tinymce.html.Writer = function(settings) {
3975
4000
  e.setAttribute(n, '' + v, 2);
3976
4001
  else
3977
4002
  e.removeAttribute(n, 2);
4003
+
4004
+ // fire onChangeAttrib event for attributes that have changed
4005
+ if (tinyMCE.activeEditor && originalValue != v) {
4006
+ var ed = tinyMCE.activeEditor;
4007
+ ed.onSetAttrib.dispatch(ed, e, n, v);
4008
+ }
3978
4009
  });
3979
4010
  },
3980
4011
 
@@ -4680,6 +4711,12 @@ tinymce.html.Writer = function(settings) {
4680
4711
  function trim(node) {
4681
4712
  var i, children = node.childNodes, type = node.nodeType;
4682
4713
 
4714
+ function surroundedBySpans(node) {
4715
+ var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
4716
+ var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
4717
+ return previousIsSpan && nextIsSpan;
4718
+ }
4719
+
4683
4720
  if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4684
4721
  return;
4685
4722
 
@@ -4690,7 +4727,10 @@ tinymce.html.Writer = function(settings) {
4690
4727
  // Keep non whitespace text nodes
4691
4728
  if (type == 3 && node.nodeValue.length > 0) {
4692
4729
  // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
4693
- if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4730
+ // Also keep text nodes with only spaces if surrounded by spans.
4731
+ // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
4732
+ var trimmedLength = tinymce.trim(node.nodeValue).length;
4733
+ if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node))
4694
4734
  return;
4695
4735
  } else if (type == 1) {
4696
4736
  // If the only child is a bookmark then move it up
@@ -4727,9 +4767,9 @@ tinymce.html.Writer = function(settings) {
4727
4767
 
4728
4768
  // Insert middle chunk
4729
4769
  if (re)
4730
- pa.replaceChild(re, e);
4731
- else
4732
- pa.insertBefore(e, pe);
4770
+ pa.replaceChild(re, e);
4771
+ else
4772
+ pa.insertBefore(e, pe);
4733
4773
 
4734
4774
  // Insert after chunk
4735
4775
  pa.insertBefore(trim(aft), pe);
@@ -8029,7 +8069,8 @@ window.tinymce.dom.Sizzle = Sizzle;
8029
8069
  }
8030
8070
 
8031
8071
  s.addRange(r);
8032
- t.selectedRange = s.getRangeAt(0);
8072
+ // adding range isn't always successful so we need to check range count otherwise an exception can occur
8073
+ t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
8033
8074
  }
8034
8075
  } else {
8035
8076
  // Is W3C Range
@@ -8141,6 +8182,11 @@ window.tinymce.dom.Sizzle = Sizzle;
8141
8182
  normalize : function() {
8142
8183
  var self = this, rng, normalized;
8143
8184
 
8185
+ // TODO:
8186
+ // Retain selection direction.
8187
+ // Lean left/right on Gecko for inline elements.
8188
+ // Run this on mouse up/key up when the user manually moves the selection
8189
+
8144
8190
  // Normalize only on non IE browsers for now
8145
8191
  if (tinymce.isIE)
8146
8192
  return;
@@ -8175,18 +8221,24 @@ window.tinymce.dom.Sizzle = Sizzle;
8175
8221
  if (node.nodeType === 3) {
8176
8222
  offset = start ? 0 : node.nodeValue.length - 1;
8177
8223
  container = node;
8224
+ normalized = true;
8178
8225
  break;
8179
8226
  }
8180
8227
 
8181
- // Found a BR element that we can place the caret before
8182
- if (node.nodeName === 'BR') {
8228
+ // Found a BR/IMG element that we can place the caret before
8229
+ if (/^(BR|IMG)$/.test(node.nodeName)) {
8183
8230
  offset = dom.nodeIndex(node);
8184
8231
  container = node.parentNode;
8232
+
8233
+ // Put caret after image when moving the end point
8234
+ if (node.nodeName == "IMG" && !start) {
8235
+ offset++;
8236
+ }
8237
+
8238
+ normalized = true;
8185
8239
  break;
8186
8240
  }
8187
8241
  } while (node = (start ? walker.next() : walker.prev()));
8188
-
8189
- normalized = true;
8190
8242
  }
8191
8243
  }
8192
8244
  }
@@ -8201,7 +8253,7 @@ window.tinymce.dom.Sizzle = Sizzle;
8201
8253
  // Normalize the end points
8202
8254
  normalizeEndPoint(true);
8203
8255
 
8204
- if (rng.collapsed)
8256
+ if (!rng.collapsed)
8205
8257
  normalizeEndPoint();
8206
8258
 
8207
8259
  // Set the selection if it was normalized
@@ -8313,12 +8365,11 @@ window.tinymce.dom.Sizzle = Sizzle;
8313
8365
  if (!settings.apply_source_formatting)
8314
8366
  settings.indent = false;
8315
8367
 
8316
- settings.remove_trailing_brs = true;
8317
-
8318
8368
  // Default DOM and Schema if they are undefined
8319
8369
  dom = dom || tinymce.DOM;
8320
8370
  schema = schema || new tinymce.html.Schema(settings);
8321
8371
  settings.entity_encoding = settings.entity_encoding || 'named';
8372
+ settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8322
8373
 
8323
8374
  onPreProcess = new tinymce.util.Dispatcher(self);
8324
8375
 
@@ -8382,8 +8433,8 @@ window.tinymce.dom.Sizzle = Sizzle;
8382
8433
  function trim(value) {
8383
8434
  return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
8384
8435
  .replace(/^[\r\n]*|[\r\n]*$/g, '')
8385
- .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
8386
- .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
8436
+ .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
8437
+ .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
8387
8438
  };
8388
8439
 
8389
8440
  while (i--) {
@@ -9684,7 +9735,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9684
9735
  // Internal functions
9685
9736
  _setupKeyboardNav : function(){
9686
9737
  var contextMenu, menuItems, t=this;
9687
- contextMenu = DOM.select('#menu_' + t.id)[0];
9738
+ contextMenu = DOM.get('menu_' + t.id);
9688
9739
  menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9689
9740
  menuItems.splice(0,0,contextMenu);
9690
9741
  t.keyboardNav = new tinymce.ui.KeyboardNavigation({
@@ -9791,10 +9842,26 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9791
9842
  },
9792
9843
 
9793
9844
  postRender : function() {
9794
- var t = this, s = t.settings;
9845
+ var t = this, s = t.settings, bookmark;
9795
9846
 
9847
+ // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
9848
+ // need to keep the selection in case the selection is lost
9849
+ if (tinymce.isIE && t.editor) {
9850
+ tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
9851
+ bookmark = t.editor.selection.getBookmark();
9852
+ });
9853
+ }
9796
9854
  tinymce.dom.Event.add(t.id, 'click', function(e) {
9797
- if (!t.isDisabled())
9855
+ if (!t.isDisabled()) {
9856
+ // restore the selection in case the selection is lost in IE
9857
+ if (tinymce.isIE && t.editor && bookmark) {
9858
+ tinymce.activeEditor.selection.moveToBookmark(bookmark);
9859
+ }
9860
+ return s.onclick.call(s.scope, e);
9861
+ }
9862
+ });
9863
+ tinymce.dom.Event.add(t.id, 'keyup', function(e) {
9864
+ if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
9798
9865
  return s.onclick.call(s.scope, e);
9799
9866
  });
9800
9867
  }
@@ -10501,15 +10568,21 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
10501
10568
  }
10502
10569
 
10503
10570
  n = DOM.add(tr, 'td');
10504
- n = DOM.add(n, 'a', {
10505
- role : 'option',
10571
+ var settings = {
10506
10572
  href : 'javascript:;',
10507
10573
  style : {
10508
10574
  backgroundColor : '#' + c
10509
10575
  },
10510
10576
  'title': t.editor.getLang('colors.' + c, c),
10511
10577
  'data-mce-color' : '#' + c
10512
- });
10578
+ };
10579
+
10580
+ // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
10581
+ if (!tinymce.isIE ) {
10582
+ settings['role']= 'option';
10583
+ }
10584
+
10585
+ n = DOM.add(n, 'a', settings);
10513
10586
 
10514
10587
  if (t.editor.forcedHighContrastMode) {
10515
10588
  n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
@@ -11145,7 +11218,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11145
11218
  Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
11146
11219
  isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
11147
11220
  ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11148
- inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
11221
+ inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;
11149
11222
 
11150
11223
  tinymce.create('tinymce.Editor', {
11151
11224
  Editor : function(id, s) {
@@ -11169,6 +11242,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11169
11242
 
11170
11243
  'onPostRender',
11171
11244
 
11245
+ 'onLoad',
11246
+
11172
11247
  'onInit',
11173
11248
 
11174
11249
  'onRemove',
@@ -11231,7 +11306,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11231
11306
 
11232
11307
  'onVisualAid',
11233
11308
 
11234
- 'onSetProgressState'
11309
+ 'onSetProgressState',
11310
+
11311
+ 'onSetAttrib'
11235
11312
  ], function(e) {
11236
11313
  t[e] = new Dispatcher(t);
11237
11314
  });
@@ -11583,7 +11660,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11583
11660
  bc = bc[t.id] || '';
11584
11661
  }
11585
11662
 
11586
- t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';
11663
+ t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
11587
11664
 
11588
11665
  // Domain relaxing enabled, then set document domain
11589
11666
  if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
@@ -12051,7 +12128,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12051
12128
  }
12052
12129
 
12053
12130
  t._refreshContentEditable();
12054
- selection.normalize();
12055
12131
 
12056
12132
  // Is not content editable
12057
12133
  if (!ce)
@@ -12823,30 +12899,32 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12823
12899
 
12824
12900
  // Add block quote deletion handler
12825
12901
  t.onKeyDown.add(function(ed, e) {
12826
- // Was the BACKSPACE key pressed?
12827
- if (e.keyCode != 8)
12902
+ if (e.keyCode != VK.BACKSPACE)
12903
+ return;
12904
+
12905
+ var rng = ed.selection.getRng();
12906
+ if (!rng.collapsed)
12828
12907
  return;
12829
12908
 
12830
- var n = ed.selection.getRng().startContainer;
12831
- var offset = ed.selection.getRng().startOffset;
12909
+ var n = rng.startContainer;
12910
+ var offset = rng.startOffset;
12832
12911
 
12833
12912
  while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
12834
12913
  n = n.parentNode;
12835
-
12914
+
12836
12915
  // Is the cursor at the beginning of a blockquote?
12837
12916
  if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
12838
12917
  // Remove the blockquote
12839
12918
  ed.formatter.toggle('blockquote', null, n.parentNode);
12840
12919
 
12841
12920
  // Move the caret to the beginning of n
12842
- var rng = ed.selection.getRng();
12843
12921
  rng.setStart(n, 0);
12844
12922
  rng.setEnd(n, 0);
12845
12923
  ed.selection.setRng(rng);
12846
12924
  ed.selection.collapse(false);
12847
12925
  }
12848
12926
  });
12849
-
12927
+
12850
12928
 
12851
12929
 
12852
12930
  // Add reset handler
@@ -13054,9 +13132,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13054
13132
  if (target !== t.getBody()) {
13055
13133
  t.dom.setAttrib(target, "style", null);
13056
13134
 
13057
- each(template, function(attr) {
13058
- target.setAttributeNode(attr.cloneNode(true));
13059
- });
13135
+ each(template, function(attr) {
13136
+ target.setAttributeNode(attr.cloneNode(true));
13137
+ });
13060
13138
  }
13061
13139
  };
13062
13140
  }
@@ -13368,6 +13446,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13368
13446
  var parser, serializer, parentNode, rootNode, fragment, args,
13369
13447
  marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13370
13448
 
13449
+ //selection.normalize();
13450
+
13371
13451
  // Setup parser and serializer
13372
13452
  parser = editor.parser;
13373
13453
  serializer = new tinymce.html.Serializer({}, editor.schema);
@@ -13594,7 +13674,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13594
13674
  addCommands({
13595
13675
  // Override justify commands
13596
13676
  'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13597
- return isFormatMatch('align' + command.substring(7));
13677
+ var name = 'align' + command.substring(7);
13678
+ // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
13679
+ // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
13680
+ var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
13681
+ var matches = tinymce.map(nodes, function(node) {
13682
+ return !!formatter.matchNode(node, name);
13683
+ });
13684
+ return tinymce.inArray(matches, TRUE) !== -1;
13598
13685
  },
13599
13686
 
13600
13687
  'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
@@ -14991,30 +15078,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14991
15078
  function apply(name, vars, node) {
14992
15079
  var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14993
15080
 
14994
- function moveStart(rng) {
14995
- var container = rng.startContainer,
14996
- offset = rng.startOffset,
14997
- walker, node;
14998
-
14999
- // Move startContainer/startOffset in to a suitable node
15000
- if (container.nodeType == 1 || container.nodeValue === "") {
15001
- container = container.nodeType == 1 ? container.childNodes[offset] : container;
15002
-
15003
- // Might fail if the offset is behind the last element in it's container
15004
- if (container) {
15005
- walker = new TreeWalker(container, container.parentNode);
15006
- for (node = walker.current(); node; node = walker.next()) {
15007
- if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
15008
- rng.setStart(node, 0);
15009
- break;
15010
- }
15011
- }
15012
- }
15013
- }
15014
-
15015
- return rng;
15016
- };
15017
-
15018
15081
  function setElementFormat(elm, fmt) {
15019
15082
  fmt = fmt || format;
15020
15083
 
@@ -15365,7 +15428,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15365
15428
  }
15366
15429
 
15367
15430
  selection.moveToBookmark(bookmark);
15368
- selection.setRng(moveStart(selection.getRng(TRUE)));
15431
+ moveStart(selection.getRng(TRUE));
15369
15432
  ed.nodeChanged();
15370
15433
  } else
15371
15434
  performCaretAction('apply', name, vars);
@@ -15375,44 +15438,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
15375
15438
 
15376
15439
  function remove(name, vars, node) {
15377
15440
  var formatList = get(name), format = formatList[0], bookmark, i, rng;
15378
- function moveStart(rng) {
15379
- var container = rng.startContainer,
15380
- offset = rng.startOffset,
15381
- walker, node, nodes, tmpNode;
15382
-
15383
- // Convert text node into index if possible
15384
- if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
15385
- container = container.parentNode;
15386
- offset = nodeIndex(container) + 1;
15387
- }
15388
-
15389
- // Move startContainer/startOffset in to a suitable node
15390
- if (container.nodeType == 1) {
15391
- nodes = container.childNodes;
15392
- container = nodes[Math.min(offset, nodes.length - 1)];
15393
- walker = new TreeWalker(container);
15394
-
15395
- // If offset is at end of the parent node walk to the next one
15396
- if (offset > nodes.length - 1)
15397
- walker.next();
15398
-
15399
- for (node = walker.current(); node; node = walker.next()) {
15400
- if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
15401
- // IE has a "neat" feature where it moves the start node into the closest element
15402
- // we can avoid this by inserting an element before it and then remove it after we set the selection
15403
- tmpNode = dom.create('a', null, INVISIBLE_CHAR);
15404
- node.parentNode.insertBefore(tmpNode, node);
15405
-
15406
- // Set selection and remove tmpNode
15407
- rng.setStart(node, 0);
15408
- selection.setRng(rng);
15409
- dom.remove(tmpNode);
15410
-
15411
- return;
15412
- }
15413
- }
15414
- }
15415
- };
15416
15441
 
15417
15442
  // Merges the styles for each node
15418
15443
  function process(node) {
@@ -16500,7 +16525,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
16500
16525
  dom.remove(node);
16501
16526
  } else {
16502
16527
  child = findFirstTextNode(node);
16503
- child = child.deleteData(0, 1);
16528
+
16529
+ if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
16530
+ child = child.deleteData(0, 1);
16531
+ }
16532
+
16504
16533
  dom.remove(node, 1);
16505
16534
  }
16506
16535
 
@@ -16630,34 +16659,39 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
16630
16659
  }
16631
16660
  };
16632
16661
 
16633
- // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16634
- ed.onBeforeGetContent.addToTop(function() {
16635
- var nodes = [], i;
16662
+ // Only bind the caret events once
16663
+ if (!self._hasCaretEvents) {
16664
+ // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16665
+ ed.onBeforeGetContent.addToTop(function() {
16666
+ var nodes = [], i;
16636
16667
 
16637
- if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16638
- // Mark children
16639
- i = nodes.length;
16640
- while (i--) {
16641
- dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16668
+ if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16669
+ // Mark children
16670
+ i = nodes.length;
16671
+ while (i--) {
16672
+ dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16673
+ }
16642
16674
  }
16643
- }
16644
- });
16675
+ });
16645
16676
 
16646
- // Remove caret container on mouse up and on key up
16647
- tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
16648
- ed[name].addToTop(function() {
16649
- removeCaretContainer();
16677
+ // Remove caret container on mouse up and on key up
16678
+ tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
16679
+ ed[name].addToTop(function() {
16680
+ removeCaretContainer();
16681
+ });
16650
16682
  });
16651
- });
16652
16683
 
16653
- // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16654
- ed.onKeyDown.addToTop(function(ed, e) {
16655
- var keyCode = e.keyCode;
16684
+ // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16685
+ ed.onKeyDown.addToTop(function(ed, e) {
16686
+ var keyCode = e.keyCode;
16656
16687
 
16657
- if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16658
- removeCaretContainer(getParentCaretContainer(selection.getStart()));
16659
- }
16660
- });
16688
+ if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16689
+ removeCaretContainer(getParentCaretContainer(selection.getStart()));
16690
+ }
16691
+ });
16692
+
16693
+ self._hasCaretEvents = true;
16694
+ }
16661
16695
 
16662
16696
  // Do apply or remove caret format
16663
16697
  if (type == "apply") {
@@ -16666,6 +16700,46 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
16666
16700
  removeCaretFormat();
16667
16701
  }
16668
16702
  };
16703
+
16704
+ function moveStart(rng) {
16705
+ var container = rng.startContainer,
16706
+ offset = rng.startOffset,
16707
+ walker, node, nodes, tmpNode;
16708
+
16709
+ // Convert text node into index if possible
16710
+ if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
16711
+ container = container.parentNode;
16712
+ offset = nodeIndex(container) + 1;
16713
+ }
16714
+
16715
+ // Move startContainer/startOffset in to a suitable node
16716
+ if (container.nodeType == 1) {
16717
+ nodes = container.childNodes;
16718
+ container = nodes[Math.min(offset, nodes.length - 1)];
16719
+ walker = new TreeWalker(container);
16720
+
16721
+ // If offset is at end of the parent node walk to the next one
16722
+ if (offset > nodes.length - 1)
16723
+ walker.next();
16724
+
16725
+ for (node = walker.current(); node; node = walker.next()) {
16726
+ if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16727
+ // IE has a "neat" feature where it moves the start node into the closest element
16728
+ // we can avoid this by inserting an element before it and then remove it after we set the selection
16729
+ tmpNode = dom.create('a', null, INVISIBLE_CHAR);
16730
+ node.parentNode.insertBefore(tmpNode, node);
16731
+
16732
+ // Set selection and remove tmpNode
16733
+ rng.setStart(node, 0);
16734
+ selection.setRng(rng);
16735
+ dom.remove(tmpNode);
16736
+
16737
+ return;
16738
+ }
16739
+ }
16740
+ }
16741
+ };
16742
+
16669
16743
  };
16670
16744
  })(tinymce);
16671
16745