tinymce-rails 3.5b3 → 3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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