tinymce-rails 3.5b3 → 3.5

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.
@@ -6,9 +6,9 @@
6
6
  var tinymce = {
7
7
  majorVersion : '3',
8
8
 
9
- minorVersion : '5b3',
9
+ minorVersion : '5',
10
10
 
11
- releaseDate : '2012-03-29',
11
+ releaseDate : '2012-05-03',
12
12
 
13
13
  _init : function() {
14
14
  var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
@@ -506,52 +506,64 @@
506
506
  tinymce.create('tinymce.util.Dispatcher', {
507
507
  scope : null,
508
508
  listeners : null,
509
+ inDispatch: false,
509
510
 
510
- Dispatcher : function(s) {
511
- this.scope = s || this;
511
+ Dispatcher : function(scope) {
512
+ this.scope = scope || this;
512
513
  this.listeners = [];
513
514
  },
514
515
 
515
- add : function(cb, s) {
516
- this.listeners.push({cb : cb, scope : s || this.scope});
516
+ add : function(callback, scope) {
517
+ this.listeners.push({cb : callback, scope : scope || this.scope});
517
518
 
518
- return cb;
519
+ return callback;
519
520
  },
520
521
 
521
- addToTop : function(cb, s) {
522
- this.listeners.unshift({cb : cb, scope : s || this.scope});
522
+ addToTop : function(callback, scope) {
523
+ var self = this, listener = {cb : callback, scope : scope || self.scope};
523
524
 
524
- return cb;
525
+ // Create new listeners if addToTop is executed in a dispatch loop
526
+ if (self.inDispatch) {
527
+ self.listeners = [listener].concat(self.listeners);
528
+ } else {
529
+ self.listeners.unshift(listener);
530
+ }
531
+
532
+ return callback;
525
533
  },
526
534
 
527
- remove : function(cb) {
528
- var l = this.listeners, o = null;
535
+ remove : function(callback) {
536
+ var listeners = this.listeners, output = null;
529
537
 
530
- tinymce.each(l, function(c, i) {
531
- if (cb == c.cb) {
532
- o = cb;
533
- l.splice(i, 1);
538
+ tinymce.each(listeners, function(listener, i) {
539
+ if (callback == listener.cb) {
540
+ output = listener;
541
+ listeners.splice(i, 1);
534
542
  return false;
535
543
  }
536
544
  });
537
545
 
538
- return o;
546
+ return output;
539
547
  },
540
548
 
541
549
  dispatch : function() {
542
- var s, a = arguments, i, li = this.listeners, c;
550
+ var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
543
551
 
552
+ self.inDispatch = true;
553
+
544
554
  // Needs to be a real loop since the listener count might change while looping
545
555
  // And this is also more efficient
546
- for (i = 0; i<li.length; i++) {
547
- c = li[i];
548
- s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);
556
+ for (i = 0; i < listeners.length; i++) {
557
+ listener = listeners[i];
558
+ returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
549
559
 
550
- if (s === false)
560
+ if (returnValue === false)
551
561
  break;
552
562
  }
553
563
 
554
- return s;
564
+ self.inDispatch = false;
565
+
566
+ return returnValue;
555
567
  }
556
568
 
557
569
  });
@@ -1134,12 +1146,8 @@ tinymce.util.Quirks = function(editor) {
1134
1146
  editor.onKeyDown.add(function(editor, e) {
1135
1147
  var isDelete;
1136
1148
 
1137
- if (e.isDefaultPrevented()) {
1138
- return;
1139
- }
1140
-
1141
1149
  isDelete = e.keyCode == DELETE;
1142
- if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1150
+ if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1143
1151
  e.preventDefault();
1144
1152
  removeMergedFormatSpans(isDelete);
1145
1153
  }
@@ -1147,33 +1155,73 @@ tinymce.util.Quirks = function(editor) {
1147
1155
 
1148
1156
  editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1149
1157
  };
1150
-
1158
+
1151
1159
  function emptyEditorWhenDeleting() {
1152
- function serializeRng(rng) {
1153
- var body = dom.create("body");
1154
- var contents = rng.cloneContents();
1155
- body.appendChild(contents);
1156
- return selection.serializer.serialize(body, {format: 'html'});
1157
- }
1160
+ function getEndPointNode(rng, start) {
1161
+ var container, offset, prefix = start ? 'start' : 'end';
1158
1162
 
1159
- function allContentsSelected(rng) {
1160
- var selection = serializeRng(rng);
1163
+ container = rng[prefix + 'Container'];
1164
+ offset = rng[prefix + 'Offset'];
1161
1165
 
1162
- var allRng = dom.createRng();
1163
- allRng.selectNode(editor.getBody());
1166
+ // Resolve indexed container
1167
+ if (container.nodeType == 1 && container.hasChildNodes()) {
1168
+ container = container.childNodes[Math.min(start ? offset : (offset > 0 ? offset - 1 : 0), container.childNodes.length - 1)]
1169
+ }
1164
1170
 
1165
- var allSelection = serializeRng(allRng);
1166
- return selection === allSelection;
1167
- }
1171
+ return container;
1172
+ };
1173
+
1174
+ function isAtStartEndOfBody(rng, start) {
1175
+ var container, offset, root, childNode, prefix = start ? 'start' : 'end', isAfter;
1176
+
1177
+ container = rng[prefix + 'Container'];
1178
+ offset = rng[prefix + 'Offset'];
1179
+ root = dom.getRoot();
1180
+
1181
+ // Resolve indexed container
1182
+ if (container.nodeType == 1) {
1183
+ isAfter = offset >= container.childNodes.length;
1184
+ container = getEndPointNode(rng, start);
1185
+
1186
+ if (container.nodeType == 3) {
1187
+ offset = start && !isAfter ? 0 : container.nodeValue.length;
1188
+ }
1189
+ }
1190
+
1191
+ // Check if start/end is in the middle of text
1192
+ if (container.nodeType == 3 && ((start && offset > 0) || (!start && offset < container.nodeValue.length))) {
1193
+ return false;
1194
+ }
1195
+
1196
+ // Walk up the DOM tree to see if the endpoint is at the beginning/end of body
1197
+ while (container !== root) {
1198
+ childNode = container.parentNode[start ? 'firstChild' : 'lastChild'];
1199
+
1200
+ // If first/last element is a BR then jump to it's sibling in case: <p>x<br></p>
1201
+ if (childNode.nodeName == "BR") {
1202
+ childNode = childNode[start ? 'nextSibling' : 'previousSibling'] || childNode;
1203
+ }
1204
+
1205
+ // If the childNode isn't the container node then break in case <p><span>A</span>[X]</p>
1206
+ if (childNode !== container) {
1207
+ return false;
1208
+ }
1209
+
1210
+ container = container.parentNode;
1211
+ }
1212
+
1213
+ return true;
1214
+ };
1168
1215
 
1169
1216
  editor.onKeyDown.addToTop(function(editor, e) {
1170
- var keyCode = e.keyCode;
1217
+ var rng, keyCode = e.keyCode;
1171
1218
 
1172
- if (keyCode == DELETE || keyCode == BACKSPACE) {
1173
- var rng = selection.getRng(true);
1219
+ if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1220
+ rng = selection.getRng(true);
1174
1221
 
1175
- if (!rng.collapsed && allContentsSelected(rng)) {
1176
- editor.setContent('', {format : 'raw'});
1222
+ if (isAtStartEndOfBody(rng, true) && isAtStartEndOfBody(rng, false) &&
1223
+ (rng.collapsed || dom.findCommonAncestor(getEndPointNode(rng, true), getEndPointNode(rng)) === dom.getRoot())) {
1224
+ editor.setContent('');
1177
1225
  editor.nodeChanged();
1178
1226
  e.preventDefault();
1179
1227
  }
@@ -1182,14 +1230,25 @@ tinymce.util.Quirks = function(editor) {
1182
1230
  };
1183
1231
 
1184
1232
  function inputMethodFocus() {
1185
- dom.bind(editor.getBody(), 'focusin', function() {
1186
- selection.setRng(selection.getRng());
1187
- });
1233
+ if (!editor.settings.content_editable) {
1234
+ // Case 1 IME doesn't initialize if you focus the document
1235
+ dom.bind(editor.getDoc(), 'focusin', function(e) {
1236
+ selection.setRng(selection.getRng());
1237
+ });
1238
+
1239
+ // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1240
+ dom.bind(editor.getDoc(), 'mousedown', function(e) {
1241
+ if (e.target == editor.getDoc().documentElement) {
1242
+ editor.getWin().focus();
1243
+ selection.setRng(selection.getRng());
1244
+ }
1245
+ });
1246
+ }
1188
1247
  };
1189
1248
 
1190
1249
  function removeHrOnBackspace() {
1191
1250
  editor.onKeyDown.add(function(editor, e) {
1192
- if (e.keyCode === BACKSPACE) {
1251
+ if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1193
1252
  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1194
1253
  var node = selection.getNode();
1195
1254
  var previousSibling = node.previousSibling;
@@ -1322,7 +1381,7 @@ tinymce.util.Quirks = function(editor) {
1322
1381
 
1323
1382
  function disableBackspaceIntoATable() {
1324
1383
  editor.onKeyDown.add(function(editor, e) {
1325
- if (e.keyCode === BACKSPACE) {
1384
+ if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1326
1385
  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1327
1386
  var previousSibling = selection.getNode().previousSibling;
1328
1387
  if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
@@ -1410,12 +1469,8 @@ tinymce.util.Quirks = function(editor) {
1410
1469
  editor.onKeyDown.add(function(editor, e) {
1411
1470
  var isDelete, rng, container, offset, brElm, sibling, collapsed;
1412
1471
 
1413
- if (e.isDefaultPrevented()) {
1414
- return;
1415
- }
1416
-
1417
1472
  isDelete = e.keyCode == DELETE;
1418
- if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1473
+ if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1419
1474
  rng = selection.getRng();
1420
1475
  container = rng.startContainer;
1421
1476
  offset = rng.startOffset;
@@ -1455,7 +1510,7 @@ tinymce.util.Quirks = function(editor) {
1455
1510
  editor.onKeyDown.add(function(editor, e) {
1456
1511
  var rng, container, offset, root, parent;
1457
1512
 
1458
- if (e.keyCode != VK.BACKSPACE) {
1513
+ if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1459
1514
  return;
1460
1515
  }
1461
1516
 
@@ -1469,7 +1524,7 @@ tinymce.util.Quirks = function(editor) {
1469
1524
  return;
1470
1525
  }
1471
1526
 
1472
- while (parent && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1527
+ while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1473
1528
  parent = parent.parentNode;
1474
1529
  }
1475
1530
 
@@ -1547,7 +1602,7 @@ tinymce.util.Quirks = function(editor) {
1547
1602
 
1548
1603
  function deleteImageOnBackSpace() {
1549
1604
  editor.onKeyDown.add(function(editor, e) {
1550
- if (e.keyCode == 8 && selection.getNode().nodeName == 'IMG') {
1605
+ if (!e.isDefaultPrevented() && e.keyCode == 8 && selection.getNode().nodeName == 'IMG') {
1551
1606
  e.preventDefault();
1552
1607
  editor.undoManager.beforeChange();
1553
1608
  dom.remove(selection.getNode());
@@ -1559,12 +1614,12 @@ tinymce.util.Quirks = function(editor) {
1559
1614
  // All browsers
1560
1615
  disableBackspaceIntoATable();
1561
1616
  removeBlockQuoteOnBackSpace();
1617
+ emptyEditorWhenDeleting();
1562
1618
 
1563
1619
  // WebKit
1564
1620
  if (tinymce.isWebKit) {
1565
1621
  keepInlineElementOnDeleteBackspace();
1566
1622
  cleanupStylesWhenDeleting();
1567
- emptyEditorWhenDeleting();
1568
1623
  inputMethodFocus();
1569
1624
  selectControlElements();
1570
1625
 
@@ -1577,7 +1632,6 @@ tinymce.util.Quirks = function(editor) {
1577
1632
  // IE
1578
1633
  if (tinymce.isIE) {
1579
1634
  removeHrOnBackspace();
1580
- emptyEditorWhenDeleting();
1581
1635
  ensureBodyHasRoleApplication();
1582
1636
  addNewLinesBeforeBrInPre();
1583
1637
  removePreSerializedStylesWhenSelectingControls();
@@ -3459,7 +3513,7 @@ tinymce.html.Styles = function(settings, schema) {
3459
3513
  self.parse = function(html, args) {
3460
3514
  var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3461
3515
  blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3462
- endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3516
+ endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3463
3517
 
3464
3518
  args = args || {};
3465
3519
  matchedNodes = {};
@@ -3474,6 +3528,7 @@ tinymce.html.Styles = function(settings, schema) {
3474
3528
  startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3475
3529
  endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3476
3530
  allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3531
+ isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3477
3532
 
3478
3533
  function addRootBlocks() {
3479
3534
  var node = rootNode.firstChild, next, rootBlockNode;
@@ -3626,10 +3681,12 @@ tinymce.html.Styles = function(settings, schema) {
3626
3681
  if (elementRule) {
3627
3682
  if (blockElements[name]) {
3628
3683
  if (!isInWhiteSpacePreservedElement) {
3629
- // Trim whitespace at beginning of block
3630
- for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
3684
+ // Trim whitespace of the first node in a block
3685
+ textNode = node.firstChild;
3686
+ if (textNode && textNode.type === 3) {
3631
3687
  text = textNode.value.replace(startWhiteSpaceRegExp, '');
3632
3688
 
3689
+ // Any characters left after trim or should we remove it
3633
3690
  if (text.length > 0) {
3634
3691
  textNode.value = text;
3635
3692
  textNode = textNode.next;
@@ -3638,12 +3695,27 @@ tinymce.html.Styles = function(settings, schema) {
3638
3695
  textNode.remove();
3639
3696
  textNode = sibling;
3640
3697
  }
3698
+
3699
+ // Remove any pure whitespace siblings
3700
+ while (textNode && textNode.type === 3) {
3701
+ text = textNode.value;
3702
+ sibling = textNode.next;
3703
+
3704
+ if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3705
+ textNode.remove();
3706
+ textNode = sibling;
3707
+ }
3708
+
3709
+ textNode = sibling;
3710
+ }
3641
3711
  }
3642
3712
 
3643
- // Trim whitespace at end of block
3644
- for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
3713
+ // Trim whitespace of the last node in a block
3714
+ textNode = node.lastChild;
3715
+ if (textNode && textNode.type === 3) {
3645
3716
  text = textNode.value.replace(endWhiteSpaceRegExp, '');
3646
3717
 
3718
+ // Any characters left after trim or should we remove it
3647
3719
  if (text.length > 0) {
3648
3720
  textNode.value = text;
3649
3721
  textNode = textNode.prev;
@@ -3652,6 +3724,19 @@ tinymce.html.Styles = function(settings, schema) {
3652
3724
  textNode.remove();
3653
3725
  textNode = sibling;
3654
3726
  }
3727
+
3728
+ // Remove any pure whitespace siblings
3729
+ while (textNode && textNode.type === 3) {
3730
+ text = textNode.value;
3731
+ sibling = textNode.prev;
3732
+
3733
+ if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3734
+ textNode.remove();
3735
+ textNode = sibling;
3736
+ }
3737
+
3738
+ textNode = sibling;
3739
+ }
3655
3740
  }
3656
3741
  }
3657
3742
 
@@ -4532,7 +4617,11 @@ tinymce.dom = {};
4532
4617
  return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4533
4618
  };
4534
4619
 
4535
- self.remove = function(target, events, func) {
4620
+ self.remove = function(target, events, func, scope) {
4621
+ if (!target) {
4622
+ return self;
4623
+ }
4624
+
4536
4625
  // Old API supported direct ID assignment
4537
4626
  if (typeof(target) === "string") {
4538
4627
  target = document.getElementById(target);
@@ -4540,7 +4629,7 @@ tinymce.dom = {};
4540
4629
 
4541
4630
  // Old API supported multiple targets
4542
4631
  if (target instanceof Array) {
4543
- var i = target;
4632
+ var i = target.length;
4544
4633
 
4545
4634
  while (i--) {
4546
4635
  self.remove(target[i], events, func, scope);
@@ -8967,6 +9056,29 @@ window.tinymce.dom.Sizzle = Sizzle;
8967
9056
  return index;
8968
9057
  };
8969
9058
 
9059
+ function normalizeTableCellSelection(rng) {
9060
+ function moveEndPoint(start) {
9061
+ var container, offset, childNodes, prefix = start ? 'start' : 'end';
9062
+
9063
+ container = rng[prefix + 'Container'];
9064
+ offset = rng[prefix + 'Offset'];
9065
+
9066
+ if (container.nodeType == 1 && container.nodeName == "TR") {
9067
+ childNodes = container.childNodes;
9068
+ container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9069
+ if (container) {
9070
+ offset = start ? 0 : container.childNodes.length;
9071
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
9072
+ }
9073
+ }
9074
+ };
9075
+
9076
+ moveEndPoint(true);
9077
+ moveEndPoint();
9078
+
9079
+ return rng;
9080
+ };
9081
+
8970
9082
  function getLocation() {
8971
9083
  var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
8972
9084
 
@@ -9061,13 +9173,8 @@ window.tinymce.dom.Sizzle = Sizzle;
9061
9173
  if (name == 'IMG')
9062
9174
  return {name : name, index : findIndex(name, element)};
9063
9175
 
9064
- // Can't insert a node into the root of document WebKit defaults to document
9065
- if (rng.startContainer.nodeType == 9) {
9066
- return;
9067
- }
9068
-
9069
9176
  // W3C method
9070
- rng2 = rng.cloneRange();
9177
+ rng2 = normalizeTableCellSelection(rng.cloneRange());
9071
9178
 
9072
9179
  // Insert end marker
9073
9180
  if (!collapsed) {
@@ -9075,6 +9182,7 @@ window.tinymce.dom.Sizzle = Sizzle;
9075
9182
  rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9076
9183
  }
9077
9184
 
9185
+ rng = normalizeTableCellSelection(rng);
9078
9186
  rng.collapse(true);
9079
9187
  rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9080
9188
  }
@@ -9299,48 +9407,58 @@ window.tinymce.dom.Sizzle = Sizzle;
9299
9407
  },
9300
9408
 
9301
9409
  getRng : function(w3c) {
9302
- var t = this, s, r, elm, doc = t.win.document;
9410
+ var self = this, selection, rng, elm, doc = self.win.document;
9303
9411
 
9304
9412
  // Found tridentSel object then we need to use that one
9305
- if (w3c && t.tridentSel)
9306
- return t.tridentSel.getRangeAt(0);
9413
+ if (w3c && self.tridentSel) {
9414
+ return self.tridentSel.getRangeAt(0);
9415
+ }
9307
9416
 
9308
9417
  try {
9309
- if (s = t.getSel())
9310
- r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
9418
+ if (selection = self.getSel()) {
9419
+ rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9420
+ }
9311
9421
  } catch (ex) {
9312
9422
  // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9313
9423
  }
9314
9424
 
9315
9425
  // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9316
- if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
9426
+ if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9317
9427
  elm = doc.selection.createRange().item(0);
9318
- r = doc.createRange();
9319
- r.setStartBefore(elm);
9320
- r.setEndAfter(elm);
9428
+ rng = doc.createRange();
9429
+ rng.setStartBefore(elm);
9430
+ rng.setEndAfter(elm);
9321
9431
  }
9322
9432
 
9323
9433
  // No range found then create an empty one
9324
9434
  // This can occur when the editor is placed in a hidden container element on Gecko
9325
9435
  // Or on IE when there was an exception
9326
- if (!r)
9327
- r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9436
+ if (!rng) {
9437
+ rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9438
+ }
9328
9439
 
9329
- if (t.selectedRange && t.explicitRange) {
9330
- if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
9440
+ // If range is at start of document then move it to start of body
9441
+ if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9442
+ elm = self.dom.getRoot();
9443
+ rng.setStart(elm, 0);
9444
+ rng.setEnd(elm, 0);
9445
+ }
9446
+
9447
+ if (self.selectedRange && self.explicitRange) {
9448
+ if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9331
9449
  // Safari, Opera and Chrome only ever select text which causes the range to change.
9332
9450
  // This lets us use the originally set range if the selection hasn't been changed by the user.
9333
- r = t.explicitRange;
9451
+ rng = self.explicitRange;
9334
9452
  } else {
9335
- t.selectedRange = null;
9336
- t.explicitRange = null;
9453
+ self.selectedRange = null;
9454
+ self.explicitRange = null;
9337
9455
  }
9338
9456
  }
9339
9457
 
9340
- return r;
9458
+ return rng;
9341
9459
  },
9342
9460
 
9343
- setRng : function(r) {
9461
+ setRng : function(r, forward) {
9344
9462
  var s, t = this;
9345
9463
 
9346
9464
  if (!t.tridentSel) {
@@ -9356,6 +9474,13 @@ window.tinymce.dom.Sizzle = Sizzle;
9356
9474
  }
9357
9475
 
9358
9476
  s.addRange(r);
9477
+
9478
+ // Forward is set to false and we have an extend function
9479
+ if (forward === false && s.extend) {
9480
+ s.collapse(r.endContainer, r.endOffset);
9481
+ s.extend(r.startContainer, r.startOffset);
9482
+ }
9483
+
9359
9484
  // adding range isn't always successful so we need to check range count otherwise an exception can occur
9360
9485
  t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9361
9486
  }
@@ -9471,12 +9596,41 @@ window.tinymce.dom.Sizzle = Sizzle;
9471
9596
  return bl;
9472
9597
  },
9473
9598
 
9599
+ isForward: function(){
9600
+ var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9601
+
9602
+ // No support for selection direction then always return true
9603
+ if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9604
+ return true;
9605
+ }
9606
+
9607
+ anchorRange = dom.createRng();
9608
+ anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9609
+ anchorRange.collapse(true);
9610
+
9611
+ focusRange = dom.createRng();
9612
+ focusRange.setStart(sel.focusNode, sel.focusOffset);
9613
+ focusRange.collapse(true);
9614
+
9615
+ return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9616
+ },
9617
+
9474
9618
  normalize : function() {
9475
- var self = this, rng, normalized, collapsed;
9619
+ var self = this, rng, normalized, collapsed, node, sibling;
9476
9620
 
9477
9621
  function normalizeEndPoint(start) {
9478
9622
  var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9479
9623
 
9624
+ function hasBrBeforeAfter(node, left) {
9625
+ var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9626
+
9627
+ while (node = walker[left ? 'prev' : 'next']()) {
9628
+ if (node.nodeName === "BR") {
9629
+ return true;
9630
+ }
9631
+ }
9632
+ };
9633
+
9480
9634
  // Walks the dom left/right to find a suitable text node to move the endpoint into
9481
9635
  // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9482
9636
  function findTextNodeRelative(left, startNode) {
@@ -9517,7 +9671,7 @@ window.tinymce.dom.Sizzle = Sizzle;
9517
9671
 
9518
9672
  // If the container is a document move it to the body element
9519
9673
  if (container.nodeType === 9) {
9520
- container = container.body;
9674
+ container = dom.getRoot();
9521
9675
  offset = 0;
9522
9676
  }
9523
9677
 
@@ -9540,7 +9694,7 @@ window.tinymce.dom.Sizzle = Sizzle;
9540
9694
  offset = 0;
9541
9695
 
9542
9696
  // Don't walk into elements that doesn't have any child nodes like a IMG
9543
- if (container.hasChildNodes()) {
9697
+ if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9544
9698
  // Walk the DOM to find a text node to place the caret at or a BR
9545
9699
  node = container;
9546
9700
  walker = new TreeWalker(container, body);
@@ -9572,7 +9726,6 @@ window.tinymce.dom.Sizzle = Sizzle;
9572
9726
  }
9573
9727
  }
9574
9728
 
9575
-
9576
9729
  // Lean the caret to the left if possible
9577
9730
  if (collapsed) {
9578
9731
  // So this: <b>x</b><i>|x</i>
@@ -9586,8 +9739,11 @@ window.tinymce.dom.Sizzle = Sizzle;
9586
9739
  // So this: <i><b></b><i>|<br></i>
9587
9740
  // Becomes: <i><b>|</b><i><br></i>
9588
9741
  // Seems that only gecko has issues with this
9589
- if (container.nodeType === 1 && container.childNodes[offset] && container.childNodes[offset].nodeName === 'BR') {
9590
- findTextNodeRelative(true, container.childNodes[offset]);
9742
+ if (container.nodeType === 1) {
9743
+ node = container.childNodes[offset];
9744
+ if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
9745
+ findTextNodeRelative(true, container.childNodes[offset]);
9746
+ }
9591
9747
  }
9592
9748
  }
9593
9749
 
@@ -9603,9 +9759,6 @@ window.tinymce.dom.Sizzle = Sizzle;
9603
9759
  rng['set' + (start ? 'Start' : 'End')](container, offset);
9604
9760
  };
9605
9761
 
9606
- // TODO:
9607
- // Retain selection direction.
9608
-
9609
9762
  // Normalize only on non IE browsers for now
9610
9763
  if (tinymce.isIE)
9611
9764
  return;
@@ -9627,7 +9780,7 @@ window.tinymce.dom.Sizzle = Sizzle;
9627
9780
  }
9628
9781
 
9629
9782
  //console.log(self.dom.dumpRng(rng));
9630
- self.setRng(rng);
9783
+ self.setRng(rng, self.isForward());
9631
9784
  }
9632
9785
  },
9633
9786
 
@@ -9771,13 +9924,13 @@ window.tinymce.dom.Sizzle = Sizzle;
9771
9924
  }
9772
9925
  });
9773
9926
 
9774
- // Remove internal classes mceItem<..>
9927
+ // Remove internal classes mceItem<..> or mceSelected
9775
9928
  htmlParser.addAttributeFilter('class', function(nodes, name) {
9776
9929
  var i = nodes.length, node, value;
9777
9930
 
9778
9931
  while (i--) {
9779
9932
  node = nodes[i];
9780
- value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
9933
+ value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
9781
9934
  node.attr('class', value.length > 0 ? value : null);
9782
9935
  }
9783
9936
  });
@@ -10851,8 +11004,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
10851
11004
  update : function() {
10852
11005
  var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
10853
11006
 
10854
- tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
10855
- th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
11007
+ tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11008
+ th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
10856
11009
 
10857
11010
  if (!DOM.boxModel)
10858
11011
  t.element.setStyles({width : tw + 2, height : th + 2});
@@ -11397,7 +11550,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11397
11550
  m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11398
11551
  menu_line : 1,
11399
11552
  'class' : t.classPrefix + 'Menu mceNoIcons',
11400
- max_width : 150,
11553
+ max_width : 250,
11401
11554
  max_height : 150
11402
11555
  });
11403
11556
 
@@ -13515,7 +13668,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
13515
13668
  },
13516
13669
 
13517
13670
  hide : function() {
13518
- var self = this, doc = t.getDoc();
13671
+ var self = this, doc = self.getDoc();
13519
13672
 
13520
13673
  // Fixed bug where IE has a blinking cursor left from the editor
13521
13674
  if (isIE && doc)
@@ -14642,9 +14795,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
14642
14795
  // Override justify commands
14643
14796
  'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14644
14797
  var name = 'align' + command.substring(7);
14645
- // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
14646
- // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
14647
- var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
14798
+ var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
14648
14799
  var matches = tinymce.map(nodes, function(node) {
14649
14800
  return !!formatter.matchNode(node, name);
14650
14801
  });
@@ -14926,7 +15077,7 @@ tinymce.ForceBlocks = function(editor) {
14926
15077
  var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
14927
15078
 
14928
15079
  function addRootBlocks() {
14929
- var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
15080
+ var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped;
14930
15081
 
14931
15082
  if (!node || node.nodeType !== 1 || !settings.forced_root_block)
14932
15083
  return;
@@ -14972,6 +15123,7 @@ tinymce.ForceBlocks = function(editor) {
14972
15123
  if (!rootBlockNode) {
14973
15124
  rootBlockNode = dom.create(settings.forced_root_block);
14974
15125
  node.parentNode.insertBefore(rootBlockNode, node);
15126
+ wrapped = true;
14975
15127
  }
14976
15128
 
14977
15129
  tempNode = node;
@@ -15003,13 +15155,16 @@ tinymce.ForceBlocks = function(editor) {
15003
15155
  }
15004
15156
  }
15005
15157
 
15006
- editor.nodeChanged();
15158
+ // Only trigger nodeChange when we wrapped nodes to prevent a forever loop
15159
+ if (wrapped) {
15160
+ editor.nodeChanged();
15161
+ }
15007
15162
  };
15008
15163
 
15009
15164
  // Force root blocks
15010
15165
  if (settings.forced_root_block) {
15011
15166
  editor.onKeyUp.add(addRootBlocks);
15012
- editor.onClick.add(addRootBlocks);
15167
+ editor.onNodeChange.add(addRootBlocks);
15013
15168
  }
15014
15169
  };
15015
15170
 
@@ -15105,6 +15260,8 @@ tinymce.ForceBlocks = function(editor) {
15105
15260
  if (v = ed.getParam('skin_variant'))
15106
15261
  s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15107
15262
 
15263
+ s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15264
+
15108
15265
  id = t.prefix + id;
15109
15266
  cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15110
15267
  c = t.controls[id] = new cls(id, s);
@@ -16218,9 +16375,8 @@ tinymce.ForceBlocks = function(editor) {
16218
16375
  if (startContainer != endContainer) {
16219
16376
  // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16220
16377
  // This will happen if you tripple click a table cell and use remove formatting
16221
- node = startContainer.firstChild;
16222
- if (startContainer.nodeName == "TD" && node) {
16223
- startContainer = node;
16378
+ if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16379
+ startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16224
16380
  }
16225
16381
 
16226
16382
  // Wrap start/end nodes in span element since these might be cloned/moved
@@ -17381,6 +17537,15 @@ tinymce.ForceBlocks = function(editor) {
17381
17537
  }
17382
17538
  });
17383
17539
 
17540
+ // Remove bogus state if they got filled by contents using editor.selection.setContent
17541
+ selection.onSetContent.add(function() {
17542
+ dom.getParent(selection.getStart(), function(node) {
17543
+ if (node.id !== caretContainerId && dom.getAttrib(node, 'data-mce-bogus') && !dom.isEmpty(node)) {
17544
+ dom.setAttrib(node, 'data-mce-bogus', null);
17545
+ }
17546
+ });
17547
+ });
17548
+
17384
17549
  self._hasCaretEvents = true;
17385
17550
  }
17386
17551