tinymce-rails 4.1.0 → 4.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d191e02ad78108864f804da97f7dc597fe15564
4
- data.tar.gz: 70e08bfa0ee8b2a1951d89f4ed161dc05b9b1752
3
+ metadata.gz: 8ed252e5480fab2a3bbb1983a01e1f957a35ab4e
4
+ data.tar.gz: ee8dea59d4d5816febe74868d41a8d3704e1bf96
5
5
  SHA512:
6
- metadata.gz: 9d0bd0d2f404e43a55e3b0dd9e16b326d78f7e7037fe5e30d1f4a15f57c41fc6142b4231ac0d901361dcdd85f2a9b92ff83d81d2ddf4630c683d83b35cd41aec
7
- data.tar.gz: c3bc79bcf6e548e0624f015439b50b32611b198c9d35b2930e2e29d6e9304f8d41d9f708cc3297922af6221e83af57f32265b3a0e5f8f07883ce39999a195706
6
+ metadata.gz: 640e3793e032a0930269af99cb9e087bb6c37e06a72ac7c96c2db5a307d740c58148424711c4c12621fa7b22385d1e97fc4a1fe777f5ef86b07c4f84eff8ff10
7
+ data.tar.gz: f52b7cdb924065fa538253e6f0fa13edb623274b86e068fca7aed80a2bc8bc8b24b036f067074d22e5beb8670abd8ca7297c54aabb9e2ca0baf91eb9eee45f5d
@@ -1,4 +1,4 @@
1
- // 4.1.0 (2014-06-18)
1
+ // 4.1.2 (2014-07-15)
2
2
 
3
3
  /**
4
4
  * Compiled inline version. (Library mode)
@@ -744,10 +744,13 @@ define("tinymce/util/Tools", [], function() {
744
744
  * @return {Array} Array object based in input.
745
745
  */
746
746
  function toArray(obj) {
747
- var array = [], i, l;
747
+ var array = obj, i, l;
748
748
 
749
- for (i = 0, l = obj.length; i < l; i++) {
750
- array[i] = obj[i];
749
+ if (!isArray(obj)) {
750
+ array = [];
751
+ for (i = 0, l = obj.length; i < l; i++) {
752
+ array[i] = obj[i];
753
+ }
751
754
  }
752
755
 
753
756
  return array;
@@ -1327,9 +1330,6 @@ define("tinymce/Env", [], function() {
1327
1330
  *
1328
1331
  * License: http://www.tinymce.com/license
1329
1332
  * Contributing: http://www.tinymce.com/contributing
1330
- *
1331
- * Some of this logic is based on jQuery code that is released under
1332
- * MIT license that grants us to sublicense it under LGPL.
1333
1333
  */
1334
1334
 
1335
1335
  /**
@@ -1342,7 +1342,6 @@ define("tinymce/Env", [], function() {
1342
1342
  * - Event binding
1343
1343
  *
1344
1344
  * This is not currently implemented:
1345
- * - Offset
1346
1345
  * - Dimension
1347
1346
  * - Ajax
1348
1347
  * - Animation
@@ -1391,8 +1390,10 @@ define("tinymce/dom/DomQuery", [
1391
1390
  var i;
1392
1391
 
1393
1392
  if (isString(sourceItem)) {
1394
- sourceItem = createFragment(sourceItem);
1393
+ sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
1395
1394
  } else if (sourceItem.length && !sourceItem.nodeType) {
1395
+ sourceItem = DomQuery.makeArray(sourceItem);
1396
+
1396
1397
  if (reverse) {
1397
1398
  for (i = sourceItem.length - 1; i >= 0; i--) {
1398
1399
  domManipulate(targetNodes, sourceItem[i], callback, reverse);
@@ -1406,9 +1407,11 @@ define("tinymce/dom/DomQuery", [
1406
1407
  return targetNodes;
1407
1408
  }
1408
1409
 
1409
- i = targetNodes.length;
1410
- while (i--) {
1411
- callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem);
1410
+ if (sourceItem.nodeType) {
1411
+ i = targetNodes.length;
1412
+ while (i--) {
1413
+ callback.call(targetNodes[i], sourceItem);
1414
+ }
1412
1415
  }
1413
1416
 
1414
1417
  return targetNodes;
@@ -1446,62 +1449,11 @@ define("tinymce/dom/DomQuery", [
1446
1449
  'class': 'className',
1447
1450
  'readonly': 'readOnly'
1448
1451
  };
1452
+ var cssFix = {
1453
+ float: 'cssFloat'
1454
+ };
1449
1455
 
1450
- var attrGetHooks = {}, attrSetHooks = {};
1451
-
1452
- function appendHooks(target, hooks) {
1453
- each(hooks, function(key, value) {
1454
- each(key.split(' '), function() {
1455
- target[this] = value;
1456
- });
1457
- });
1458
- }
1459
-
1460
- if (Env.ie && Env.ie <= 7) {
1461
- appendHooks(attrGetHooks, {
1462
- maxlength: function(elm, value) {
1463
- value = elm.maxLength;
1464
-
1465
- if (value === 0x7fffffff) {
1466
- return undef;
1467
- }
1468
-
1469
- return value;
1470
- },
1471
-
1472
- size: function(elm, value) {
1473
- value = elm.size;
1474
-
1475
- if (value === 20) {
1476
- return undef;
1477
- }
1478
-
1479
- return value;
1480
- },
1481
-
1482
- 'class': function(elm) {
1483
- return elm.className;
1484
- },
1485
-
1486
- style: function(elm) {
1487
- if (elm.style.cssText.length === 0) {
1488
- return undef;
1489
- }
1490
-
1491
- return elm.style.cssText;
1492
- }
1493
- });
1494
-
1495
- appendHooks(attrSetHooks, {
1496
- 'class': function(elm, value) {
1497
- elm.className = value;
1498
- },
1499
-
1500
- style: function(elm, value) {
1501
- elm.style.cssText = value;
1502
- }
1503
- });
1504
- }
1456
+ var attrHooks = {}, cssHooks = {};
1505
1457
 
1506
1458
  function DomQuery(selector, context) {
1507
1459
  /*eslint new-cap:0 */
@@ -1561,6 +1513,30 @@ define("tinymce/dom/DomQuery", [
1561
1513
  return obj;
1562
1514
  }
1563
1515
 
1516
+ function grep(array, callback) {
1517
+ var out = [];
1518
+
1519
+ each(array, function(i, item) {
1520
+ if (callback(item, i)) {
1521
+ out.push(item);
1522
+ }
1523
+ });
1524
+
1525
+ return out;
1526
+ }
1527
+
1528
+ function getElementDocument(element) {
1529
+ if (!element) {
1530
+ return doc;
1531
+ }
1532
+
1533
+ if (element.nodeType == 9) {
1534
+ return element;
1535
+ }
1536
+
1537
+ return element.ownerDocument;
1538
+ }
1539
+
1564
1540
  DomQuery.fn = DomQuery.prototype = {
1565
1541
  constructor: DomQuery,
1566
1542
 
@@ -1631,14 +1607,18 @@ define("tinymce/dom/DomQuery", [
1631
1607
 
1632
1608
  if (match) {
1633
1609
  if (match[1]) {
1634
- node = createFragment(selector, context).firstChild;
1610
+ node = createFragment(selector, getElementDocument(context)).firstChild;
1635
1611
 
1636
1612
  while (node) {
1637
1613
  push.call(self, node);
1638
1614
  node = node.nextSibling;
1639
1615
  }
1640
1616
  } else {
1641
- node = doc.getElementById(match[2]);
1617
+ node = getElementDocument(context).getElementById(match[2]);
1618
+
1619
+ if (!node) {
1620
+ return self;
1621
+ }
1642
1622
 
1643
1623
  if (node.id !== match[2]) {
1644
1624
  return self.find(selector);
@@ -1718,9 +1698,10 @@ define("tinymce/dom/DomQuery", [
1718
1698
  var hook;
1719
1699
 
1720
1700
  if (this.nodeType === 1) {
1721
- hook = attrSetHooks[name];
1722
- if (hook) {
1723
- hook(this, value, name);
1701
+ hook = attrHooks[name];
1702
+ if (hook && hook.set) {
1703
+ hook.set(this, value);
1704
+ return;
1724
1705
  }
1725
1706
 
1726
1707
  if (value === null) {
@@ -1732,17 +1713,17 @@ define("tinymce/dom/DomQuery", [
1732
1713
  });
1733
1714
  } else {
1734
1715
  if (self[0] && self[0].nodeType === 1) {
1716
+ hook = attrHooks[name];
1717
+ if (hook && hook.get) {
1718
+ return hook.get(self[0], name);
1719
+ }
1720
+
1735
1721
  if (booleanMap[name]) {
1736
1722
  return self.prop(name) ? name : undef;
1737
1723
  }
1738
1724
 
1739
1725
  value = self[0].getAttribute(name, 2);
1740
1726
 
1741
- hook = attrGetHooks[name];
1742
- if (hook) {
1743
- return hook(self[0], value, name);
1744
- }
1745
-
1746
1727
  if (value === null) {
1747
1728
  value = undef;
1748
1729
  }
@@ -1808,19 +1789,28 @@ define("tinymce/dom/DomQuery", [
1808
1789
  * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
1809
1790
  */
1810
1791
  css: function(name, value) {
1811
- var self = this;
1792
+ var self = this, elm, hook;
1793
+
1794
+ function camel(name) {
1795
+ return name.replace(/-(\D)/g, function(a, b) {
1796
+ return b.toUpperCase();
1797
+ });
1798
+ }
1799
+
1800
+ function dashed(name) {
1801
+ return name.replace(/[A-Z]/g, function(a) {
1802
+ return '-' + a;
1803
+ });
1804
+ }
1812
1805
 
1813
1806
  if (typeof name === "object") {
1814
1807
  each(name, function(name, value) {
1815
1808
  self.css(name, value);
1816
1809
  });
1817
1810
  } else {
1818
- // Camelcase it, if needed
1819
- name = name.replace(/-(\D)/g, function(a, b) {
1820
- return b.toUpperCase();
1821
- });
1822
-
1823
1811
  if (isDefined(value)) {
1812
+ name = camel(name);
1813
+
1824
1814
  // Default px suffix on these
1825
1815
  if (typeof(value) === 'number' && !numericCssMap[name]) {
1826
1816
  value += 'px';
@@ -1829,31 +1819,42 @@ define("tinymce/dom/DomQuery", [
1829
1819
  self.each(function() {
1830
1820
  var style = this.style;
1831
1821
 
1832
- // IE specific opacity
1833
- if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") {
1834
- style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
1822
+ hook = cssHooks[name];
1823
+ if (hook && hook.set) {
1824
+ hook.set(this, value);
1825
+ return;
1835
1826
  }
1836
1827
 
1837
1828
  try {
1838
- style[name] = value;
1829
+ this.style[cssFix[name] || name] = value;
1839
1830
  } catch (ex) {
1840
1831
  // Ignore
1841
1832
  }
1833
+
1834
+ if (value === null || value === '') {
1835
+ if (style.removeProperty) {
1836
+ style.removeProperty(dashed(name));
1837
+ } else {
1838
+ style.removeAttribute(name);
1839
+ }
1840
+ }
1842
1841
  });
1843
1842
  } else {
1844
- if (self.context.defaultView) {
1845
- // Remove camelcase
1846
- name = name.replace(/[A-Z]/g, function(a) {
1847
- return '-' + a;
1848
- });
1843
+ elm = self[0];
1844
+
1845
+ hook = cssHooks[name];
1846
+ if (hook && hook.get) {
1847
+ return hook.get(elm);
1848
+ }
1849
1849
 
1850
+ if (elm.ownerDocument.defaultView) {
1850
1851
  try {
1851
- return self.context.defaultView.getComputedStyle(self[0], null).getPropertyValue(name);
1852
+ return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
1852
1853
  } catch (ex) {
1853
1854
  return undef;
1854
1855
  }
1855
- } else if (self[0].currentStyle) {
1856
- return self[0].currentStyle[name];
1856
+ } else if (elm.currentStyle) {
1857
+ return elm.currentStyle[camel(name)];
1857
1858
  }
1858
1859
  }
1859
1860
  }
@@ -2105,10 +2106,8 @@ define("tinymce/dom/DomQuery", [
2105
2106
  * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
2106
2107
  */
2107
2108
  unwrap: function() {
2108
- return this.each(function() {
2109
- var parentNode = DomQuery(this.parentNode);
2110
- parentNode.before(parentNode.contents());
2111
- parentNode.remove();
2109
+ return this.parent().each(function() {
2110
+ DomQuery(this).replaceWith(this.childNodes);
2112
2111
  });
2113
2112
  },
2114
2113
 
@@ -2342,10 +2341,16 @@ define("tinymce/dom/DomQuery", [
2342
2341
  * Filters the current set with the specified selector.
2343
2342
  *
2344
2343
  * @method filter
2345
- * @param {String} selector Selector to filter elements by.
2344
+ * @param {String/function} selector Selector to filter elements by.
2346
2345
  * @return {tinymce.dom.DomQuery} Set with filtered elements.
2347
2346
  */
2348
2347
  filter: function(selector) {
2348
+ if (typeof selector == 'function') {
2349
+ return DomQuery(grep(this.toArray(), function(item, i) {
2350
+ return selector(i, item);
2351
+ }));
2352
+ }
2353
+
2349
2354
  return DomQuery(DomQuery.filter(selector, this.toArray()));
2350
2355
  },
2351
2356
 
@@ -2353,15 +2358,22 @@ define("tinymce/dom/DomQuery", [
2353
2358
  * Gets the current node or any partent matching the specified selector.
2354
2359
  *
2355
2360
  * @method closest
2356
- * @param {String} selector Selector to get element by.
2361
+ * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
2357
2362
  * @return {tinymce.dom.DomQuery} Set with closest elements.
2358
2363
  */
2359
2364
  closest: function(selector) {
2360
2365
  var result = [];
2361
2366
 
2367
+ if (selector instanceof DomQuery) {
2368
+ selector = selector[0];
2369
+ }
2370
+
2362
2371
  this.each(function(i, node) {
2363
2372
  while (node) {
2364
- if (selector.nodeType && node == selector || DomQuery(node).is(selector)) {
2373
+ if (typeof selector == 'string' && DomQuery(node).is(selector)) {
2374
+ result.push(node);
2375
+ break;
2376
+ } else if (node == selector) {
2365
2377
  result.push(node);
2366
2378
  break;
2367
2379
  }
@@ -2373,6 +2385,40 @@ define("tinymce/dom/DomQuery", [
2373
2385
  return DomQuery(result);
2374
2386
  },
2375
2387
 
2388
+ /**
2389
+ * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
2390
+ *
2391
+ * @method offset
2392
+ * @param {Object} offset Optional offset object to set on each item.
2393
+ * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
2394
+ */
2395
+ offset: function(offset) {
2396
+ var elm, doc, docElm;
2397
+ var x = 0, y = 0, pos;
2398
+
2399
+ if (!offset) {
2400
+ elm = this[0];
2401
+
2402
+ if (elm) {
2403
+ doc = elm.ownerDocument;
2404
+ docElm = doc.documentElement;
2405
+
2406
+ if (elm.getBoundingClientRect) {
2407
+ pos = elm.getBoundingClientRect();
2408
+ x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
2409
+ y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
2410
+ }
2411
+ }
2412
+
2413
+ return {
2414
+ left: x,
2415
+ top: y
2416
+ };
2417
+ }
2418
+
2419
+ return this.css(offset);
2420
+ },
2421
+
2376
2422
  push: push,
2377
2423
  sort: [].sort,
2378
2424
  splice: [].splice
@@ -2443,6 +2489,21 @@ define("tinymce/dom/DomQuery", [
2443
2489
  */
2444
2490
  trim: trim,
2445
2491
 
2492
+ /**
2493
+ * Filters out items from the input array by calling the specified function for each item.
2494
+ * If the function returns false the item will be excluded if it returns true it will be included.
2495
+ *
2496
+ * @static
2497
+ * @method grep
2498
+ * @param {Array} array Array of items to loop though.
2499
+ * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
2500
+ * @return {Array} New array with values imported and filtered based in input.
2501
+ * @example
2502
+ * // Filter out some items, this will return an array with 4 and 5
2503
+ * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
2504
+ */
2505
+ grep: grep,
2506
+
2446
2507
  // Sizzle
2447
2508
  find: Sizzle,
2448
2509
  expr: Sizzle.selectors,
@@ -2467,7 +2528,21 @@ define("tinymce/dom/DomQuery", [
2467
2528
  function dir(el, prop, until) {
2468
2529
  var matched = [], cur = el[prop];
2469
2530
 
2470
- while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) {
2531
+ if (typeof until != 'string' && until instanceof DomQuery) {
2532
+ until = until[0];
2533
+ }
2534
+
2535
+ while (cur && cur.nodeType !== 9) {
2536
+ if (until !== undefined) {
2537
+ if (cur === until) {
2538
+ break;
2539
+ }
2540
+
2541
+ if (typeof until == 'string' && DomQuery(cur).is(until)) {
2542
+ break;
2543
+ }
2544
+ }
2545
+
2471
2546
  if (cur.nodeType === 1) {
2472
2547
  matched.push(cur);
2473
2548
  }
@@ -2481,13 +2556,23 @@ define("tinymce/dom/DomQuery", [
2481
2556
  function sibling(node, siblingName, nodeType, until) {
2482
2557
  var result = [];
2483
2558
 
2559
+ if (until instanceof DomQuery) {
2560
+ until = until[0];
2561
+ }
2562
+
2484
2563
  for (; node; node = node[siblingName]) {
2485
2564
  if (nodeType && node.nodeType !== nodeType) {
2486
2565
  continue;
2487
2566
  }
2488
2567
 
2489
- if (until && ((until.nodeType && node === until) || (DomQuery(node).is(until)))) {
2490
- break;
2568
+ if (until !== undefined) {
2569
+ if (node === until) {
2570
+ break;
2571
+ }
2572
+
2573
+ if (typeof until == 'string' && DomQuery(node).is(until)) {
2574
+ break;
2575
+ }
2491
2576
  }
2492
2577
 
2493
2578
  result.push(node);
@@ -2531,18 +2616,6 @@ define("tinymce/dom/DomQuery", [
2531
2616
  return dir(node, "parentNode");
2532
2617
  },
2533
2618
 
2534
- /**
2535
- * Returns a new collection with the all the parents until the matching selector/element
2536
- * of each item in current collection matching the optional selector.
2537
- *
2538
- * @method parentsUntil
2539
- * @param {String/Element} until Until the matching selector or element.
2540
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2541
- */
2542
- parentsUntil: function(node, until) {
2543
- return dir(node, "parentNode", until);
2544
- },
2545
-
2546
2619
  /**
2547
2620
  * Returns a new collection with next sibling of each item in current collection matching the optional selector.
2548
2621
  *
@@ -2566,49 +2639,96 @@ define("tinymce/dom/DomQuery", [
2566
2639
  },
2567
2640
 
2568
2641
  /**
2569
- * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
2642
+ * Returns all child elements matching the optional selector.
2570
2643
  *
2571
- * @method nextUntil
2572
- * @param {String/Element} until Until the matching selector or element.
2644
+ * @method children
2645
+ * @param {String} selector Selector to match the elements against.
2573
2646
  * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2574
2647
  */
2575
- nextUntil: function(node, selector) {
2576
- return sibling(node, 'nextSibling', 1, selector).slice(1);
2648
+ children: function(node) {
2649
+ return sibling(node.firstChild, 'nextSibling', 1);
2577
2650
  },
2578
2651
 
2579
2652
  /**
2580
- * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
2653
+ * Returns all child nodes matching the optional selector.
2581
2654
  *
2582
- * @method prevUntil
2583
- * @param {String/Element} until Until the matching selector or element.
2655
+ * @method contents
2584
2656
  * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2585
2657
  */
2586
- prevUntil: function(node, selector) {
2587
- return sibling(node, 'previousSibling', 1, selector).slice(1);
2658
+ contents: function(node) {
2659
+ return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
2660
+ }
2661
+ }, function(name, fn) {
2662
+ DomQuery.fn[name] = function(selector) {
2663
+ var self = this, result = [];
2664
+
2665
+ self.each(function() {
2666
+ var nodes = fn.call(result, this, selector, result);
2667
+
2668
+ if (nodes) {
2669
+ if (DomQuery.isArray(nodes)) {
2670
+ result.push.apply(result, nodes);
2671
+ } else {
2672
+ result.push(nodes);
2673
+ }
2674
+ }
2675
+ });
2676
+
2677
+ // If traversing on multiple elements we might get the same elements twice
2678
+ if (this.length > 1) {
2679
+ result = DomQuery.unique(result);
2680
+
2681
+ if (name.indexOf('parents') === 0) {
2682
+ result = result.reverse();
2683
+ }
2684
+ }
2685
+
2686
+ result = DomQuery(result);
2687
+
2688
+ if (selector) {
2689
+ return result.filter(selector);
2690
+ }
2691
+
2692
+ return result;
2693
+ };
2694
+ });
2695
+
2696
+ each({
2697
+ /**
2698
+ * Returns a new collection with the all the parents until the matching selector/element
2699
+ * of each item in current collection matching the optional selector.
2700
+ *
2701
+ * @method parentsUntil
2702
+ * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2703
+ * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2704
+ */
2705
+ parentsUntil: function(node, until) {
2706
+ return dir(node, "parentNode", until);
2588
2707
  },
2589
2708
 
2590
2709
  /**
2591
- * Returns all child elements matching the optional selector.
2710
+ * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
2592
2711
  *
2593
- * @method children
2594
- * @param {String} selector Selector to match the elements against.
2712
+ * @method nextUntil
2713
+ * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2595
2714
  * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2596
2715
  */
2597
- children: function(node) {
2598
- return sibling(node.firstChild, 'nextSibling', 1);
2716
+ nextUntil: function(node, until) {
2717
+ return sibling(node, 'nextSibling', 1, until).slice(1);
2599
2718
  },
2600
2719
 
2601
2720
  /**
2602
- * Returns all child nodes matching the optional selector.
2721
+ * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
2603
2722
  *
2604
- * @method contents
2723
+ * @method prevUntil
2724
+ * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2605
2725
  * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2606
2726
  */
2607
- contents: function(node) {
2608
- return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
2727
+ prevUntil: function(node, until) {
2728
+ return sibling(node, 'previousSibling', 1, until).slice(1);
2609
2729
  }
2610
2730
  }, function(name, fn) {
2611
- DomQuery.fn[name] = function(selector) {
2731
+ DomQuery.fn[name] = function(selector, filter) {
2612
2732
  var self = this, result = [];
2613
2733
 
2614
2734
  self.each(function() {
@@ -2623,16 +2743,19 @@ define("tinymce/dom/DomQuery", [
2623
2743
  }
2624
2744
  });
2625
2745
 
2626
- result = DomQuery.unique(result);
2746
+ // If traversing on multiple elements we might get the same elements twice
2747
+ if (this.length > 1) {
2748
+ result = DomQuery.unique(result);
2627
2749
 
2628
- if (name.indexOf('parents') === 0 || name === 'prevUntil') {
2629
- result = result.reverse();
2750
+ if (name.indexOf('parents') === 0 || name === 'prevUntil') {
2751
+ result = result.reverse();
2752
+ }
2630
2753
  }
2631
2754
 
2632
2755
  result = DomQuery(result);
2633
2756
 
2634
- if (selector && name.indexOf("Until") == -1) {
2635
- return result.filter(selector);
2757
+ if (filter) {
2758
+ return result.filter(filter);
2636
2759
  }
2637
2760
 
2638
2761
  return result;
@@ -2657,7 +2780,16 @@ define("tinymce/dom/DomQuery", [
2657
2780
 
2658
2781
  function jQuerySub(selector, context) {
2659
2782
  defaults = defaults || callback();
2660
- return new jQuerySub.fn.init(selector || defaults.element, context || defaults.context);
2783
+
2784
+ if (arguments.length === 0) {
2785
+ selector = defaults.element;
2786
+ }
2787
+
2788
+ if (!context) {
2789
+ context = defaults.context;
2790
+ }
2791
+
2792
+ return new jQuerySub.fn.init(selector, context);
2661
2793
  }
2662
2794
 
2663
2795
  DomQuery.extend(jQuerySub, this);
@@ -2665,38 +2797,113 @@ define("tinymce/dom/DomQuery", [
2665
2797
  return jQuerySub;
2666
2798
  };
2667
2799
 
2668
- return DomQuery;
2669
- });
2800
+ function appendHooks(targetHooks, prop, hooks) {
2801
+ each(hooks, function(name, func) {
2802
+ targetHooks[name] = targetHooks[name] || {};
2803
+ targetHooks[name][prop] = func;
2804
+ });
2805
+ }
2670
2806
 
2671
- // Included from: js/tinymce/classes/html/Styles.js
2807
+ if (Env.ie && Env.ie < 8) {
2808
+ appendHooks(attrHooks, 'get', {
2809
+ maxlength: function(elm) {
2810
+ var value = elm.maxLength;
2672
2811
 
2673
- /**
2674
- * Styles.js
2675
- *
2676
- * Copyright, Moxiecode Systems AB
2677
- * Released under LGPL License.
2678
- *
2679
- * License: http://www.tinymce.com/license
2680
- * Contributing: http://www.tinymce.com/contributing
2681
- */
2812
+ if (value === 0x7fffffff) {
2813
+ return undef;
2814
+ }
2682
2815
 
2683
- /**
2684
- * This class is used to parse CSS styles it also compresses styles to reduce the output size.
2685
- *
2686
- * @example
2687
- * var Styles = new tinymce.html.Styles({
2688
- * url_converter: function(url) {
2689
- * return url;
2690
- * }
2691
- * });
2692
- *
2693
- * styles = Styles.parse('border: 1px solid red');
2694
- * styles.color = 'red';
2695
- *
2696
- * console.log(new tinymce.html.StyleSerializer().serialize(styles));
2697
- *
2698
- * @class tinymce.html.Styles
2699
- * @version 3.4
2816
+ return value;
2817
+ },
2818
+
2819
+ size: function(elm) {
2820
+ var value = elm.size;
2821
+
2822
+ if (value === 20) {
2823
+ return undef;
2824
+ }
2825
+
2826
+ return value;
2827
+ },
2828
+
2829
+ 'class': function(elm) {
2830
+ return elm.className;
2831
+ },
2832
+
2833
+ style: function(elm) {
2834
+ var value = elm.style.cssText;
2835
+
2836
+ if (value.length === 0) {
2837
+ return undef;
2838
+ }
2839
+
2840
+ return value;
2841
+ }
2842
+ });
2843
+
2844
+ appendHooks(attrHooks, 'set', {
2845
+ 'class': function(elm, value) {
2846
+ elm.className = value;
2847
+ },
2848
+
2849
+ style: function(elm, value) {
2850
+ elm.style.cssText = value;
2851
+ }
2852
+ });
2853
+ }
2854
+
2855
+ if (Env.ie && Env.ie < 9) {
2856
+ cssFix.float = 'styleFloat';
2857
+
2858
+ appendHooks(cssHooks, 'set', {
2859
+ opacity: function(elm, value) {
2860
+ var style = elm.style;
2861
+
2862
+ if (value === null || value === '') {
2863
+ style.removeAttribute('filter');
2864
+ } else {
2865
+ style.zoom = 1;
2866
+ style.filter = 'alpha(opacity=' + (value * 100) + ')';
2867
+ }
2868
+ }
2869
+ });
2870
+ }
2871
+
2872
+ DomQuery.attrHooks = attrHooks;
2873
+ DomQuery.cssHooks = cssHooks;
2874
+
2875
+ return DomQuery;
2876
+ });
2877
+
2878
+ // Included from: js/tinymce/classes/html/Styles.js
2879
+
2880
+ /**
2881
+ * Styles.js
2882
+ *
2883
+ * Copyright, Moxiecode Systems AB
2884
+ * Released under LGPL License.
2885
+ *
2886
+ * License: http://www.tinymce.com/license
2887
+ * Contributing: http://www.tinymce.com/contributing
2888
+ */
2889
+
2890
+ /**
2891
+ * This class is used to parse CSS styles it also compresses styles to reduce the output size.
2892
+ *
2893
+ * @example
2894
+ * var Styles = new tinymce.html.Styles({
2895
+ * url_converter: function(url) {
2896
+ * return url;
2897
+ * }
2898
+ * });
2899
+ *
2900
+ * styles = Styles.parse('border: 1px solid red');
2901
+ * styles.color = 'red';
2902
+ *
2903
+ * console.log(new tinymce.html.StyleSerializer().serialize(styles));
2904
+ *
2905
+ * @class tinymce.html.Styles
2906
+ * @version 3.4
2700
2907
  */
2701
2908
  define("tinymce/html/Styles", [], function() {
2702
2909
  return function(settings, schema) {
@@ -3050,30 +3257,44 @@ define("tinymce/html/Styles", [], function() {
3050
3257
  * TreeWalker class enables you to walk the DOM in a linear manner.
3051
3258
  *
3052
3259
  * @class tinymce.dom.TreeWalker
3260
+ * @example
3261
+ * var walker = new tinymce.dom.TreeWalker(startNode);
3262
+ *
3263
+ * do {
3264
+ * console.log(walker.current());
3265
+ * } while (walker.next());
3053
3266
  */
3054
3267
  define("tinymce/dom/TreeWalker", [], function() {
3055
- return function(start_node, root_node) {
3056
- var node = start_node;
3268
+ /**
3269
+ * Constructs a new TreeWalker instance.
3270
+ *
3271
+ * @constructor
3272
+ * @method TreeWalker
3273
+ * @param {Node} startNode Node to start walking from.
3274
+ * @param {node} rootNode Optional root node to never walk out of.
3275
+ */
3276
+ return function(startNode, rootNode) {
3277
+ var node = startNode;
3057
3278
 
3058
- function findSibling(node, start_name, sibling_name, shallow) {
3279
+ function findSibling(node, startName, siblingName, shallow) {
3059
3280
  var sibling, parent;
3060
3281
 
3061
3282
  if (node) {
3062
3283
  // Walk into nodes if it has a start
3063
- if (!shallow && node[start_name]) {
3064
- return node[start_name];
3284
+ if (!shallow && node[startName]) {
3285
+ return node[startName];
3065
3286
  }
3066
3287
 
3067
3288
  // Return the sibling if it has one
3068
- if (node != root_node) {
3069
- sibling = node[sibling_name];
3289
+ if (node != rootNode) {
3290
+ sibling = node[siblingName];
3070
3291
  if (sibling) {
3071
3292
  return sibling;
3072
3293
  }
3073
3294
 
3074
3295
  // Walk up the parents to look for siblings
3075
- for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
3076
- sibling = parent[sibling_name];
3296
+ for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
3297
+ sibling = parent[siblingName];
3077
3298
  if (sibling) {
3078
3299
  return sibling;
3079
3300
  }
@@ -4385,13 +4606,61 @@ define("tinymce/dom/DOMUtils", [
4385
4606
  "tinymce/Env",
4386
4607
  "tinymce/util/Tools",
4387
4608
  "tinymce/dom/StyleSheetLoader"
4388
- ], function(Sizzle, DomQuery, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
4609
+ ], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
4389
4610
  // Shorten names
4390
- var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend;
4391
- var isWebKit = Env.webkit, isIE = Env.ie;
4611
+ var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
4612
+ var isIE = Env.ie;
4392
4613
  var simpleSelectorRe = /^([a-z0-9],?)+$/i;
4393
4614
  var whiteSpaceRegExp = /^[ \t\r\n]*$/;
4394
- var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
4615
+
4616
+ function setupAttrHooks(domUtils, settings) {
4617
+ var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
4618
+
4619
+ keepUrlHook = {
4620
+ set: function($elm, value, name) {
4621
+ if (settings.url_converter) {
4622
+ value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
4623
+ }
4624
+
4625
+ $elm.attr('data-mce-' + name, value).attr(name, value);
4626
+ },
4627
+
4628
+ get: function($elm, name) {
4629
+ return $elm.attr('data-mce-' + name) || $elm.attr(name);
4630
+ }
4631
+ };
4632
+
4633
+ attrHooks = {
4634
+ style: {
4635
+ set: function($elm, value) {
4636
+ if (value !== null && typeof value === 'object') {
4637
+ $elm.css(value);
4638
+ return;
4639
+ }
4640
+
4641
+ if (keepValues) {
4642
+ $elm.attr('data-mce-style', value);
4643
+ }
4644
+
4645
+ $elm.attr('style', value);
4646
+ },
4647
+
4648
+ get: function($elm) {
4649
+ var value = $elm.attr('data-mce-style') || $elm.attr('style');
4650
+
4651
+ value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
4652
+
4653
+ return value;
4654
+ }
4655
+ }
4656
+ };
4657
+
4658
+ if (keepValues) {
4659
+ attrHooks.href = attrHooks.src = keepUrlHook;
4660
+ }
4661
+
4662
+ return attrHooks;
4663
+ }
4395
4664
 
4396
4665
  /**
4397
4666
  * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
@@ -4410,15 +4679,9 @@ define("tinymce/dom/DOMUtils", [
4410
4679
  self.counter = 0;
4411
4680
  self.stdMode = !isIE || doc.documentMode >= 8;
4412
4681
  self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
4413
- self.hasOuterHTML = "outerHTML" in doc.createElement("a");
4414
4682
  self.styleSheetLoader = new StyleSheetLoader(doc);
4415
- this.boundEvents = [];
4416
-
4417
- self.settings = settings = extend({
4418
- keep_values: false,
4419
- hex_colors: 1
4420
- }, settings);
4421
-
4683
+ self.boundEvents = [];
4684
+ self.settings = settings = settings || {};
4422
4685
  self.schema = settings.schema;
4423
4686
  self.styles = new Styles({
4424
4687
  url_converter: settings.url_converter,
@@ -4427,8 +4690,9 @@ define("tinymce/dom/DOMUtils", [
4427
4690
 
4428
4691
  self.fixDoc(doc);
4429
4692
  self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
4693
+ self.attrHooks = setupAttrHooks(self, settings);
4430
4694
  blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
4431
- self.$ = DomQuery.overrideDefaults(function() {
4695
+ self.$ = $.overrideDefaults(function() {
4432
4696
  return {
4433
4697
  context: doc,
4434
4698
  element: self.getRoot()
@@ -4461,22 +4725,16 @@ define("tinymce/dom/DOMUtils", [
4461
4725
  }
4462
4726
 
4463
4727
  DOMUtils.prototype = {
4464
- root: null,
4465
- props: {
4466
- "for": "htmlFor",
4467
- "class": "className",
4468
- className: "className",
4469
- checked: "checked",
4470
- disabled: "disabled",
4471
- maxlength: "maxLength",
4472
- readonly: "readOnly",
4473
- selected: "selected",
4474
- value: "value",
4475
- id: "id",
4476
- name: "name",
4477
- type: "type"
4728
+ $$: function(elm) {
4729
+ if (typeof elm == 'string') {
4730
+ elm = this.get(elm);
4731
+ }
4732
+
4733
+ return this.$(elm);
4478
4734
  },
4479
4735
 
4736
+ root: null,
4737
+
4480
4738
  fixDoc: function(doc) {
4481
4739
  var settings = this.settings, name;
4482
4740
 
@@ -4518,19 +4776,7 @@ define("tinymce/dom/DOMUtils", [
4518
4776
 
4519
4777
  return clone;
4520
4778
  }
4521
- /*
4522
- // Setup HTML5 patched document fragment
4523
- if (!self.frag) {
4524
- self.frag = doc.createDocumentFragment();
4525
- self.fixDoc(self.frag);
4526
- }
4527
4779
 
4528
- // Make a deep copy by adding it to the document fragment then removing it this removed the :section
4529
- clone = doc.createElement('div');
4530
- self.frag.appendChild(clone);
4531
- clone.innerHTML = node.outerHTML;
4532
- self.frag.removeChild(clone);
4533
- */
4534
4780
  return clone.firstChild;
4535
4781
  },
4536
4782
 
@@ -4544,7 +4790,7 @@ define("tinymce/dom/DOMUtils", [
4544
4790
  getRoot: function() {
4545
4791
  var self = this;
4546
4792
 
4547
- return self.get(self.settings.root_element) || self.doc.body;
4793
+ return self.settings.root_element || self.doc.body;
4548
4794
  },
4549
4795
 
4550
4796
  /**
@@ -4757,8 +5003,8 @@ define("tinymce/dom/DOMUtils", [
4757
5003
  select: function(selector, scope) {
4758
5004
  var self = this;
4759
5005
 
4760
- //Sizzle.selectors.cacheLength = 0;
4761
- return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []);
5006
+ /*eslint new-cap:0 */
5007
+ return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
4762
5008
  },
4763
5009
 
4764
5010
  /**
@@ -4799,6 +5045,8 @@ define("tinymce/dom/DOMUtils", [
4799
5045
  }
4800
5046
 
4801
5047
  var elms = elm.nodeType ? [elm] : elm;
5048
+
5049
+ /*eslint new-cap:0 */
4802
5050
  return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
4803
5051
  },
4804
5052
 
@@ -4916,7 +5164,7 @@ define("tinymce/dom/DOMUtils", [
4916
5164
  *
4917
5165
  * @method remove
4918
5166
  * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
4919
- * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be
5167
+ * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
4920
5168
  * placed at the location of the removed element.
4921
5169
  * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
4922
5170
  * were passed in.
@@ -4927,27 +5175,26 @@ define("tinymce/dom/DOMUtils", [
4927
5175
  * // Removes an element by id in the document
4928
5176
  * tinymce.DOM.remove('mydiv');
4929
5177
  */
4930
- remove: function(node, keep_children) {
4931
- return this.run(node, function(node) {
4932
- var child, parent = node.parentNode;
5178
+ remove: function(node, keepChildren) {
5179
+ node = this.$$(node);
4933
5180
 
4934
- if (!parent) {
4935
- return null;
4936
- }
5181
+ if (keepChildren) {
5182
+ node.each(function() {
5183
+ var child;
4937
5184
 
4938
- if (keep_children) {
4939
- while ((child = node.firstChild)) {
4940
- // IE 8 will crash if you don't remove completely empty text nodes
4941
- if (!isIE || child.nodeType !== 3 || child.nodeValue) {
4942
- parent.insertBefore(child, node);
5185
+ while ((child = this.firstChild)) {
5186
+ if (child.nodeType == 3 && child.data.length === 0) {
5187
+ this.removeChild(child);
4943
5188
  } else {
4944
- node.removeChild(child);
5189
+ this.parentNode.insertBefore(child, this);
4945
5190
  }
4946
5191
  }
4947
- }
5192
+ }).remove();
5193
+ } else {
5194
+ node.remove();
5195
+ }
4948
5196
 
4949
- return parent.removeChild(node);
4950
- });
5197
+ return node.length > 1 ? node.toArray() : node[0];
4951
5198
  },
4952
5199
 
4953
5200
  /**
@@ -4966,50 +5213,11 @@ define("tinymce/dom/DOMUtils", [
4966
5213
  * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
4967
5214
  */
4968
5215
  setStyle: function(elm, name, value) {
4969
- return this.run(elm, function(elm) {
4970
- var self = this, style, key;
4971
-
4972
- if (name) {
4973
- if (typeof(name) === 'string') {
4974
- style = elm.style;
4975
-
4976
- // Camelcase it, if needed
4977
- name = name.replace(/-(\D)/g, function(a, b) {
4978
- return b.toUpperCase();
4979
- });
4980
-
4981
- // Default px suffix on these
4982
- if (((typeof(value) === 'number') || /^[\-0-9\.]+$/.test(value)) && !numericCssMap[name]) {
4983
- value += 'px';
4984
- }
4985
-
4986
- // IE specific opacity
4987
- if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") {
4988
- style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
4989
- }
4990
-
4991
- if (name == "float") {
4992
- // Old IE vs modern browsers
4993
- name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat";
4994
- }
4995
-
4996
- try {
4997
- style[name] = value;
4998
- } catch (ex) {
4999
- // Ignore IE errors
5000
- }
5216
+ elm = this.$$(elm).css(name, value);
5001
5217
 
5002
- // Force update of the style data
5003
- if (self.settings.update_styles) {
5004
- elm.removeAttribute('data-mce-style');
5005
- }
5006
- } else {
5007
- for (key in name) {
5008
- self.setStyle(elm, key, name[key]);
5009
- }
5010
- }
5011
- }
5012
- });
5218
+ if (this.settings.update_styles) {
5219
+ elm.attr('data-mce-style', null);
5220
+ }
5013
5221
  },
5014
5222
 
5015
5223
  /**
@@ -5022,25 +5230,10 @@ define("tinymce/dom/DOMUtils", [
5022
5230
  * @return {String} Current style or computed style value of an element.
5023
5231
  */
5024
5232
  getStyle: function(elm, name, computed) {
5025
- elm = this.get(elm);
5026
-
5027
- if (!elm) {
5028
- return;
5029
- }
5030
-
5031
- // W3C
5032
- if (this.doc.defaultView && computed) {
5033
- // Remove camelcase
5034
- name = name.replace(/[A-Z]/g, function(a) {
5035
- return '-' + a;
5036
- });
5233
+ elm = this.$$(elm);
5037
5234
 
5038
- try {
5039
- return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name);
5040
- } catch (ex) {
5041
- // Old safari might fail
5042
- return null;
5043
- }
5235
+ if (computed) {
5236
+ return elm.css(name);
5044
5237
  }
5045
5238
 
5046
5239
  // Camelcase it, if needed
@@ -5052,12 +5245,7 @@ define("tinymce/dom/DOMUtils", [
5052
5245
  name = isIE ? 'styleFloat' : 'cssFloat';
5053
5246
  }
5054
5247
 
5055
- // IE & Opera
5056
- if (elm.currentStyle && computed) {
5057
- return elm.currentStyle[name];
5058
- }
5059
-
5060
- return elm.style ? elm.style[name] : undefined;
5248
+ return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
5061
5249
  },
5062
5250
 
5063
5251
  /**
@@ -5074,11 +5262,7 @@ define("tinymce/dom/DOMUtils", [
5074
5262
  * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
5075
5263
  */
5076
5264
  setStyles: function(elm, styles) {
5077
- this.setStyle(elm, styles);
5078
- },
5079
-
5080
- css: function(elm, name, value) {
5081
- this.setStyle(elm, name, value);
5265
+ this.$$(elm).css(styles);
5082
5266
  },
5083
5267
 
5084
5268
  /**
@@ -5111,73 +5295,29 @@ define("tinymce/dom/DOMUtils", [
5111
5295
  * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
5112
5296
  */
5113
5297
  setAttrib: function(elm, name, value) {
5114
- var self = this;
5298
+ var self = this, originalValue, hook, settings = self.settings;
5115
5299
 
5116
- // What's the point
5117
- if (!elm || !name) {
5118
- return;
5300
+ if (value === '') {
5301
+ value = null;
5119
5302
  }
5120
5303
 
5121
- return this.run(elm, function(elm) {
5122
- var settings = self.settings;
5123
- var originalValue = elm.getAttribute(name);
5124
-
5125
- if (value !== null) {
5126
- switch (name) {
5127
- case "style":
5128
- if (!is(value, 'string')) {
5129
- each(value, function(value, name) {
5130
- self.setStyle(elm, name, value);
5131
- });
5132
-
5133
- return;
5134
- }
5135
-
5136
- // No mce_style for elements with these since they might get resized by the user
5137
- if (settings.keep_values) {
5138
- if (value) {
5139
- elm.setAttribute('data-mce-style', value, 2);
5140
- } else {
5141
- elm.removeAttribute('data-mce-style', 2);
5142
- }
5143
- }
5144
-
5145
- elm.style.cssText = value;
5146
- break;
5147
-
5148
- case "class":
5149
- elm.className = value || ''; // Fix IE null bug
5150
- break;
5151
-
5152
- case "src":
5153
- case "href":
5154
- if (settings.keep_values) {
5155
- if (settings.url_converter) {
5156
- value = settings.url_converter.call(settings.url_converter_scope || self, value, name, elm);
5157
- }
5158
-
5159
- self.setAttrib(elm, 'data-mce-' + name, value, 2);
5160
- }
5161
-
5162
- break;
5163
-
5164
- case "shape":
5165
- elm.setAttribute('data-mce-style', value);
5166
- break;
5167
- }
5168
- }
5304
+ elm = self.$$(elm);
5305
+ originalValue = elm.attr(name);
5169
5306
 
5170
- if (is(value) && value !== null && value.length !== 0) {
5171
- elm.setAttribute(name, '' + value, 2);
5172
- } else {
5173
- elm.removeAttribute(name, 2);
5174
- }
5307
+ hook = self.attrHooks[name];
5308
+ if (hook && hook.set) {
5309
+ hook.set(elm, value, name);
5310
+ } else {
5311
+ elm.attr(name, value);
5312
+ }
5175
5313
 
5176
- // fire onChangeAttrib event for attributes that have changed
5177
- if (originalValue != value && settings.onSetAttrib) {
5178
- settings.onSetAttrib({attrElm: elm, attrName: name, attrValue: value});
5179
- }
5180
- });
5314
+ if (originalValue != value && settings.onSetAttrib) {
5315
+ settings.onSetAttrib({
5316
+ attrElm: elm,
5317
+ attrName: name,
5318
+ attrValue: value
5319
+ });
5320
+ }
5181
5321
  },
5182
5322
 
5183
5323
  /**
@@ -5196,9 +5336,9 @@ define("tinymce/dom/DOMUtils", [
5196
5336
  setAttribs: function(elm, attrs) {
5197
5337
  var self = this;
5198
5338
 
5199
- return this.run(elm, function(elm) {
5339
+ self.$$(elm).each(function(i, node) {
5200
5340
  each(attrs, function(value, name) {
5201
- self.setAttrib(elm, name, value);
5341
+ self.setAttrib(node, name, value);
5202
5342
  });
5203
5343
  });
5204
5344
  },
@@ -5213,186 +5353,69 @@ define("tinymce/dom/DOMUtils", [
5213
5353
  * @return {String} Attribute value string, default value or null if the attribute wasn't found.
5214
5354
  */
5215
5355
  getAttrib: function(elm, name, defaultVal) {
5216
- var value, self = this, undef;
5356
+ var self = this, hook, value;
5217
5357
 
5218
- elm = self.get(elm);
5358
+ elm = self.$$(elm);
5219
5359
 
5220
- if (!elm || elm.nodeType !== 1) {
5221
- return defaultVal === undef ? false : defaultVal;
5360
+ hook = self.attrHooks[name];
5361
+ if (hook && hook.get) {
5362
+ value = hook.get(elm, name);
5363
+ } else {
5364
+ value = elm.attr(name);
5222
5365
  }
5223
5366
 
5224
- if (!is(defaultVal)) {
5225
- defaultVal = '';
5367
+ if (typeof value == 'undefined') {
5368
+ value = defaultVal || '';
5226
5369
  }
5227
5370
 
5228
- // Try the mce variant for these
5229
- if (/^(src|href|style|coords|shape)$/.test(name)) {
5230
- value = elm.getAttribute("data-mce-" + name);
5371
+ return value;
5372
+ },
5231
5373
 
5232
- if (value) {
5233
- return value;
5234
- }
5235
- }
5374
+ /**
5375
+ * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
5376
+ *
5377
+ * @method getPos
5378
+ * @param {Element/String} elm HTML element or element id to get x, y position from.
5379
+ * @param {Element} rootElm Optional root element to stop calculations at.
5380
+ * @return {object} Absolute position of the specified element object with x, y fields.
5381
+ */
5382
+ getPos: function(elm, rootElm) {
5383
+ var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
5236
5384
 
5237
- if (isIE && self.props[name]) {
5238
- value = elm[self.props[name]];
5239
- value = value && value.nodeValue ? value.nodeValue : value;
5240
- }
5385
+ elm = self.get(elm);
5386
+ rootElm = rootElm || doc.body;
5241
5387
 
5242
- if (!value) {
5243
- value = elm.getAttribute(name, 2);
5244
- }
5388
+ if (elm) {
5389
+ // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5390
+ if (rootElm === doc.body && elm.getBoundingClientRect) {
5391
+ pos = elm.getBoundingClientRect();
5392
+ rootElm = self.boxModel ? doc.documentElement : doc.body;
5245
5393
 
5246
- // Check boolean attribs
5247
- if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) {
5248
- if (elm[self.props[name]] === true && value === '') {
5249
- return name;
5394
+ // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5395
+ // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5396
+ x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
5397
+ y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;
5398
+
5399
+ return {x: x, y: y};
5250
5400
  }
5251
5401
 
5252
- return value ? name : '';
5253
- }
5402
+ offsetParent = elm;
5403
+ while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5404
+ x += offsetParent.offsetLeft || 0;
5405
+ y += offsetParent.offsetTop || 0;
5406
+ offsetParent = offsetParent.offsetParent;
5407
+ }
5254
5408
 
5255
- // Inner input elements will override attributes on form elements
5256
- if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) {
5257
- return elm.getAttributeNode(name).nodeValue;
5409
+ offsetParent = elm.parentNode;
5410
+ while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5411
+ x -= offsetParent.scrollLeft || 0;
5412
+ y -= offsetParent.scrollTop || 0;
5413
+ offsetParent = offsetParent.parentNode;
5414
+ }
5258
5415
  }
5259
5416
 
5260
- if (name === 'style') {
5261
- value = value || elm.style.cssText;
5262
-
5263
- if (value) {
5264
- value = self.serializeStyle(self.parseStyle(value), elm.nodeName);
5265
-
5266
- if (self.settings.keep_values) {
5267
- elm.setAttribute('data-mce-style', value);
5268
- }
5269
- }
5270
- }
5271
-
5272
- // Remove Apple and WebKit stuff
5273
- if (isWebKit && name === "class" && value) {
5274
- value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5275
- }
5276
-
5277
- // Handle IE issues
5278
- if (isIE) {
5279
- switch (name) {
5280
- case 'rowspan':
5281
- case 'colspan':
5282
- // IE returns 1 as default value
5283
- if (value === 1) {
5284
- value = '';
5285
- }
5286
-
5287
- break;
5288
-
5289
- case 'size':
5290
- // IE returns +0 as default value for size
5291
- if (value === '+0' || value === 20 || value === 0) {
5292
- value = '';
5293
- }
5294
-
5295
- break;
5296
-
5297
- case 'width':
5298
- case 'height':
5299
- case 'vspace':
5300
- case 'checked':
5301
- case 'disabled':
5302
- case 'readonly':
5303
- if (value === 0) {
5304
- value = '';
5305
- }
5306
-
5307
- break;
5308
-
5309
- case 'hspace':
5310
- // IE returns -1 as default value
5311
- if (value === -1) {
5312
- value = '';
5313
- }
5314
-
5315
- break;
5316
-
5317
- case 'maxlength':
5318
- case 'tabindex':
5319
- // IE returns default value
5320
- if (value === 32768 || value === 2147483647 || value === '32768') {
5321
- value = '';
5322
- }
5323
-
5324
- break;
5325
-
5326
- case 'multiple':
5327
- case 'compact':
5328
- case 'noshade':
5329
- case 'nowrap':
5330
- if (value === 65535) {
5331
- return name;
5332
- }
5333
-
5334
- return defaultVal;
5335
-
5336
- case 'shape':
5337
- value = value.toLowerCase();
5338
- break;
5339
-
5340
- default:
5341
- // IE has odd anonymous function for event attributes
5342
- if (name.indexOf('on') === 0 && value) {
5343
- value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
5344
- }
5345
- }
5346
- }
5347
-
5348
- return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal;
5349
- },
5350
-
5351
- /**
5352
- * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
5353
- *
5354
- * @method getPos
5355
- * @param {Element/String} elm HTML element or element id to get x, y position from.
5356
- * @param {Element} rootElm Optional root element to stop calculations at.
5357
- * @return {object} Absolute position of the specified element object with x, y fields.
5358
- */
5359
- getPos: function(elm, rootElm) {
5360
- var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
5361
-
5362
- elm = self.get(elm);
5363
- rootElm = rootElm || doc.body;
5364
-
5365
- if (elm) {
5366
- // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5367
- if (rootElm === doc.body && elm.getBoundingClientRect) {
5368
- pos = elm.getBoundingClientRect();
5369
- rootElm = self.boxModel ? doc.documentElement : doc.body;
5370
-
5371
- // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5372
- // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5373
- x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
5374
- y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;
5375
-
5376
- return {x: x, y: y};
5377
- }
5378
-
5379
- offsetParent = elm;
5380
- while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5381
- x += offsetParent.offsetLeft || 0;
5382
- y += offsetParent.offsetTop || 0;
5383
- offsetParent = offsetParent.offsetParent;
5384
- }
5385
-
5386
- offsetParent = elm.parentNode;
5387
- while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5388
- x -= offsetParent.scrollLeft || 0;
5389
- y -= offsetParent.scrollTop || 0;
5390
- offsetParent = offsetParent.parentNode;
5391
- }
5392
- }
5393
-
5394
- return {x: x, y: y};
5395
- },
5417
+ return {x: x, y: y};
5418
+ },
5396
5419
 
5397
5420
  /**
5398
5421
  * Parses the specified style value into an object collection. This parser will also
@@ -5539,22 +5562,7 @@ define("tinymce/dom/DOMUtils", [
5539
5562
  * tinymce.DOM.addClass('mydiv', 'myclass');
5540
5563
  */
5541
5564
  addClass: function(elm, cls) {
5542
- return this.run(elm, function(elm) {
5543
- var clsVal;
5544
-
5545
- if (!cls) {
5546
- return 0;
5547
- }
5548
-
5549
- if (this.hasClass(elm, cls)) {
5550
- return elm.className;
5551
- }
5552
-
5553
- clsVal = this.removeClass(elm, cls);
5554
- elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls;
5555
-
5556
- return clsVal;
5557
- });
5565
+ this.$$(elm).addClass(cls);
5558
5566
  },
5559
5567
 
5560
5568
  /**
@@ -5573,32 +5581,7 @@ define("tinymce/dom/DOMUtils", [
5573
5581
  * tinymce.DOM.removeClass('mydiv', 'myclass');
5574
5582
  */
5575
5583
  removeClass: function(elm, cls) {
5576
- var self = this, re;
5577
-
5578
- return self.run(elm, function(elm) {
5579
- var val;
5580
-
5581
- if (self.hasClass(elm, cls)) {
5582
- if (!re) {
5583
- re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g");
5584
- }
5585
-
5586
- val = elm.className.replace(re, ' ');
5587
- val = trim(val != ' ' ? val : '');
5588
-
5589
- elm.className = val;
5590
-
5591
- // Empty class attr
5592
- if (!val) {
5593
- elm.removeAttribute('class');
5594
- elm.removeAttribute('className');
5595
- }
5596
-
5597
- return val;
5598
- }
5599
-
5600
- return elm.className;
5601
- });
5584
+ this.toggleClass(elm, cls, false);
5602
5585
  },
5603
5586
 
5604
5587
  /**
@@ -5610,13 +5593,7 @@ define("tinymce/dom/DOMUtils", [
5610
5593
  * @return {Boolean} true/false if the specified element has the specified class.
5611
5594
  */
5612
5595
  hasClass: function(elm, cls) {
5613
- elm = this.get(elm);
5614
-
5615
- if (!elm || !cls) {
5616
- return false;
5617
- }
5618
-
5619
- return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1;
5596
+ return this.$$(elm).hasClass(cls);
5620
5597
  },
5621
5598
 
5622
5599
  /**
@@ -5628,15 +5605,11 @@ define("tinymce/dom/DOMUtils", [
5628
5605
  * @param {[type]} state Optional state to set.
5629
5606
  */
5630
5607
  toggleClass: function(elm, cls, state) {
5631
- state = state === undefined ? !this.hasClass(elm, cls) : state;
5632
-
5633
- if (this.hasClass(elm, cls) !== state) {
5634
- if (state) {
5635
- this.addClass(elm, cls);
5636
- } else {
5637
- this.removeClass(elm, cls);
5608
+ this.$$(elm).toggleClass(cls, state).each(function() {
5609
+ if (this.className === '') {
5610
+ $(this).attr('class', null);
5638
5611
  }
5639
- }
5612
+ });
5640
5613
  },
5641
5614
 
5642
5615
  /**
@@ -5646,7 +5619,7 @@ define("tinymce/dom/DOMUtils", [
5646
5619
  * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
5647
5620
  */
5648
5621
  show: function(elm) {
5649
- return this.setStyle(elm, 'display', 'block');
5622
+ this.$$(elm).show();
5650
5623
  },
5651
5624
 
5652
5625
  /**
@@ -5659,7 +5632,7 @@ define("tinymce/dom/DOMUtils", [
5659
5632
  * tinymce.DOM.hide('myid');
5660
5633
  */
5661
5634
  hide: function(elm) {
5662
- return this.setStyle(elm, 'display', 'none');
5635
+ this.$$(elm).hide();
5663
5636
  },
5664
5637
 
5665
5638
  /**
@@ -5670,9 +5643,7 @@ define("tinymce/dom/DOMUtils", [
5670
5643
  * @return {Boolean} true/false if the element is hidden or not.
5671
5644
  */
5672
5645
  isHidden: function(elm) {
5673
- elm = this.get(elm);
5674
-
5675
- return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none';
5646
+ return this.$$(elm).css('display') == 'none';
5676
5647
  },
5677
5648
 
5678
5649
  /**
@@ -5692,7 +5663,7 @@ define("tinymce/dom/DOMUtils", [
5692
5663
  * URLs will get converted, hex color values fixed etc. Check processHTML for details.
5693
5664
  *
5694
5665
  * @method setHTML
5695
- * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of.
5666
+ * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
5696
5667
  * @param {String} h HTML content to set as inner HTML of the element.
5697
5668
  * @example
5698
5669
  * // Sets the inner HTML of all paragraphs in the active editor
@@ -5701,44 +5672,35 @@ define("tinymce/dom/DOMUtils", [
5701
5672
  * // Sets the inner HTML of an element by id in the document
5702
5673
  * tinymce.DOM.setHTML('mydiv', 'some inner html');
5703
5674
  */
5704
- setHTML: function(element, html) {
5705
- var self = this;
5675
+ setHTML: function(elm, html) {
5676
+ elm = this.$$(elm);
5677
+
5678
+ if (isIE) {
5679
+ elm.each(function(i, target) {
5680
+ if (target.canHaveHTML === false) {
5681
+ return;
5682
+ }
5706
5683
 
5707
- return self.run(element, function(element) {
5708
- if (isIE) {
5709
5684
  // Remove all child nodes, IE keeps empty text nodes in DOM
5710
- while (element.firstChild) {
5711
- element.removeChild(element.firstChild);
5685
+ while (target.firstChild) {
5686
+ target.removeChild(target.firstChild);
5712
5687
  }
5713
5688
 
5714
5689
  try {
5715
5690
  // IE will remove comments from the beginning
5716
5691
  // unless you padd the contents with something
5717
- element.innerHTML = '<br />' + html;
5718
- element.removeChild(element.firstChild);
5692
+ target.innerHTML = '<br>' + html;
5693
+ target.removeChild(target.firstChild);
5719
5694
  } catch (ex) {
5720
- // IE sometimes produces an unknown runtime error on innerHTML if it's a block element
5721
- // within a block element for example a div inside a p
5722
- // This seems to fix this problem
5723
-
5724
- // Create new div with HTML contents and a BR in front to keep comments
5725
- var newElement = self.create('div');
5726
- newElement.innerHTML = '<br />' + html;
5727
-
5728
- // Add all children from div to target
5729
- each(grep(newElement.childNodes), function(node, i) {
5730
- // Skip br element
5731
- if (i && element.canHaveHTML) {
5732
- element.appendChild(node);
5733
- }
5734
- });
5695
+ // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
5696
+ $('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
5735
5697
  }
5736
- } else {
5737
- element.innerHTML = html;
5738
- }
5739
5698
 
5740
- return html;
5741
- });
5699
+ return html;
5700
+ });
5701
+ } else {
5702
+ elm.html(html);
5703
+ }
5742
5704
  },
5743
5705
 
5744
5706
  /**
@@ -5752,22 +5714,8 @@ define("tinymce/dom/DOMUtils", [
5752
5714
  * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
5753
5715
  */
5754
5716
  getOuterHTML: function(elm) {
5755
- var doc, self = this;
5756
-
5757
- elm = self.get(elm);
5758
-
5759
- if (!elm) {
5760
- return null;
5761
- }
5762
-
5763
- if (elm.nodeType === 1 && self.hasOuterHTML) {
5764
- return elm.outerHTML;
5765
- }
5766
-
5767
- doc = (elm.ownerDocument || self.doc).createElement("body");
5768
- doc.appendChild(elm.cloneNode(true));
5769
-
5770
- return doc.innerHTML;
5717
+ elm = this.get(elm);
5718
+ return elm.nodeType == 1 ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
5771
5719
  },
5772
5720
 
5773
5721
  /**
@@ -5784,44 +5732,15 @@ define("tinymce/dom/DOMUtils", [
5784
5732
  * // Sets the outer HTML of an element by id in the document
5785
5733
  * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
5786
5734
  */
5787
- setOuterHTML: function(elm, html, doc) {
5735
+ setOuterHTML: function(elm, html) {
5788
5736
  var self = this;
5789
5737
 
5790
- return self.run(elm, function(elm) {
5791
- function set() {
5792
- var node, tempElm;
5793
-
5794
- tempElm = doc.createElement("body");
5795
- tempElm.innerHTML = html;
5796
-
5797
- node = tempElm.lastChild;
5798
- while (node) {
5799
- self.insertAfter(node.cloneNode(true), elm);
5800
- node = node.previousSibling;
5801
- }
5802
-
5803
- self.remove(elm);
5804
- }
5805
-
5806
- // Only set HTML on elements
5807
- if (elm.nodeType == 1) {
5808
- doc = doc || elm.ownerDocument || self.doc;
5809
-
5810
- if (isIE) {
5811
- try {
5812
- // Try outerHTML for IE it sometimes produces an unknown runtime error
5813
- if (elm.nodeType == 1 && self.hasOuterHTML) {
5814
- elm.outerHTML = html;
5815
- } else {
5816
- set();
5817
- }
5818
- } catch (ex) {
5819
- // Fix for unknown runtime error
5820
- set();
5821
- }
5822
- } else {
5823
- set();
5824
- }
5738
+ self.$$(elm).each(function() {
5739
+ try {
5740
+ this.outerHTML = html;
5741
+ } catch (ex) {
5742
+ // OuterHTML for IE it sometimes produces an "unknown runtime error"
5743
+ self.remove($(this).html(html), true);
5825
5744
  }
5826
5745
  });
5827
5746
  },
@@ -5852,14 +5771,14 @@ define("tinymce/dom/DOMUtils", [
5852
5771
  * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
5853
5772
  * @return {Element/Array} Element that got added or an array with elements.
5854
5773
  */
5855
- insertAfter: function(node, reference_node) {
5856
- reference_node = this.get(reference_node);
5774
+ insertAfter: function(node, referenceNode) {
5775
+ referenceNode = this.get(referenceNode);
5857
5776
 
5858
5777
  return this.run(node, function(node) {
5859
5778
  var parent, nextSibling;
5860
5779
 
5861
- parent = reference_node.parentNode;
5862
- nextSibling = reference_node.nextSibling;
5780
+ parent = referenceNode.parentNode;
5781
+ nextSibling = referenceNode.nextSibling;
5863
5782
 
5864
5783
  if (nextSibling) {
5865
5784
  parent.insertBefore(node, nextSibling);
@@ -5914,8 +5833,8 @@ define("tinymce/dom/DOMUtils", [
5914
5833
  newElm = self.create(name);
5915
5834
 
5916
5835
  // Copy attribs to new block
5917
- each(self.getAttribs(elm), function(attr_node) {
5918
- self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName));
5836
+ each(self.getAttribs(elm), function(attrNode) {
5837
+ self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
5919
5838
  });
5920
5839
 
5921
5840
  // Replace block
@@ -6995,10 +6914,10 @@ define("tinymce/AddOnManager", [
6995
6914
  * });
6996
6915
  */
6997
6916
 
6998
- // Included from: js/tinymce/classes/html/Node.js
6917
+ // Included from: js/tinymce/classes/dom/RangeUtils.js
6999
6918
 
7000
6919
  /**
7001
- * Node.js
6920
+ * RangeUtils.js
7002
6921
  *
7003
6922
  * Copyright, Moxiecode Systems AB
7004
6923
  * Released under LGPL License.
@@ -7008,88 +6927,719 @@ define("tinymce/AddOnManager", [
7008
6927
  */
7009
6928
 
7010
6929
  /**
7011
- * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
7012
- *
7013
- * @example
7014
- * var node = new tinymce.html.Node('strong', 1);
7015
- * someRoot.append(node);
6930
+ * This class contains a few utility methods for ranges.
7016
6931
  *
7017
- * @class tinymce.html.Node
7018
- * @version 3.4
6932
+ * @class tinymce.dom.RangeUtils
6933
+ * @private
7019
6934
  */
7020
- define("tinymce/html/Node", [], function() {
7021
- var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
7022
- '#text': 3,
7023
- '#comment': 8,
7024
- '#cdata': 4,
7025
- '#pi': 7,
7026
- '#doctype': 10,
7027
- '#document-fragment': 11
7028
- };
7029
-
7030
- // Walks the tree left/right
7031
- function walk(node, root_node, prev) {
7032
- var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
7033
-
7034
- // Walk into nodes if it has a start
7035
- if (node[startName]) {
7036
- return node[startName];
7037
- }
7038
-
7039
- // Return the sibling if it has one
7040
- if (node !== root_node) {
7041
- sibling = node[siblingName];
6935
+ define("tinymce/dom/RangeUtils", [
6936
+ "tinymce/util/Tools",
6937
+ "tinymce/dom/TreeWalker"
6938
+ ], function(Tools, TreeWalker) {
6939
+ var each = Tools.each;
7042
6940
 
7043
- if (sibling) {
7044
- return sibling;
7045
- }
6941
+ function getEndChild(container, index) {
6942
+ var childNodes = container.childNodes;
7046
6943
 
7047
- // Walk up the parents to look for siblings
7048
- for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
7049
- sibling = parent[siblingName];
6944
+ index--;
7050
6945
 
7051
- if (sibling) {
7052
- return sibling;
7053
- }
7054
- }
6946
+ if (index > childNodes.length - 1) {
6947
+ index = childNodes.length - 1;
6948
+ } else if (index < 0) {
6949
+ index = 0;
7055
6950
  }
7056
- }
7057
-
7058
- /**
7059
- * Constructs a new Node instance.
7060
- *
7061
- * @constructor
7062
- * @method Node
7063
- * @param {String} name Name of the node type.
7064
- * @param {Number} type Numeric type representing the node.
7065
- */
7066
- function Node(name, type) {
7067
- this.name = name;
7068
- this.type = type;
7069
6951
 
7070
- if (type === 1) {
7071
- this.attributes = [];
7072
- this.attributes.map = {};
7073
- }
6952
+ return childNodes[index] || container;
7074
6953
  }
7075
6954
 
7076
- Node.prototype = {
6955
+ function RangeUtils(dom) {
7077
6956
  /**
7078
- * Replaces the current node with the specified one.
7079
- *
7080
- * @example
7081
- * someNode.replace(someNewNode);
6957
+ * Walks the specified range like object and executes the callback for each sibling collection it finds.
7082
6958
  *
7083
- * @method replace
7084
- * @param {tinymce.html.Node} node Node to replace the current node with.
7085
- * @return {tinymce.html.Node} The old node that got replaced.
6959
+ * @method walk
6960
+ * @param {Object} rng Range like object.
6961
+ * @param {function} callback Callback function to execute for each sibling collection.
7086
6962
  */
7087
- replace: function(node) {
7088
- var self = this;
7089
-
7090
- if (node.parent) {
7091
- node.remove();
7092
- }
6963
+ this.walk = function(rng, callback) {
6964
+ var startContainer = rng.startContainer,
6965
+ startOffset = rng.startOffset,
6966
+ endContainer = rng.endContainer,
6967
+ endOffset = rng.endOffset,
6968
+ ancestor, startPoint,
6969
+ endPoint, node, parent, siblings, nodes;
6970
+
6971
+ // Handle table cell selection the table plugin enables
6972
+ // you to fake select table cells and perform formatting actions on them
6973
+ nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
6974
+ if (nodes.length > 0) {
6975
+ each(nodes, function(node) {
6976
+ callback([node]);
6977
+ });
6978
+
6979
+ return;
6980
+ }
6981
+
6982
+ /**
6983
+ * Excludes start/end text node if they are out side the range
6984
+ *
6985
+ * @private
6986
+ * @param {Array} nodes Nodes to exclude items from.
6987
+ * @return {Array} Array with nodes excluding the start/end container if needed.
6988
+ */
6989
+ function exclude(nodes) {
6990
+ var node;
6991
+
6992
+ // First node is excluded
6993
+ node = nodes[0];
6994
+ if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
6995
+ nodes.splice(0, 1);
6996
+ }
6997
+
6998
+ // Last node is excluded
6999
+ node = nodes[nodes.length - 1];
7000
+ if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
7001
+ nodes.splice(nodes.length - 1, 1);
7002
+ }
7003
+
7004
+ return nodes;
7005
+ }
7006
+
7007
+ /**
7008
+ * Collects siblings
7009
+ *
7010
+ * @private
7011
+ * @param {Node} node Node to collect siblings from.
7012
+ * @param {String} name Name of the sibling to check for.
7013
+ * @return {Array} Array of collected siblings.
7014
+ */
7015
+ function collectSiblings(node, name, end_node) {
7016
+ var siblings = [];
7017
+
7018
+ for (; node && node != end_node; node = node[name]) {
7019
+ siblings.push(node);
7020
+ }
7021
+
7022
+ return siblings;
7023
+ }
7024
+
7025
+ /**
7026
+ * Find an end point this is the node just before the common ancestor root.
7027
+ *
7028
+ * @private
7029
+ * @param {Node} node Node to start at.
7030
+ * @param {Node} root Root/ancestor element to stop just before.
7031
+ * @return {Node} Node just before the root element.
7032
+ */
7033
+ function findEndPoint(node, root) {
7034
+ do {
7035
+ if (node.parentNode == root) {
7036
+ return node;
7037
+ }
7038
+
7039
+ node = node.parentNode;
7040
+ } while (node);
7041
+ }
7042
+
7043
+ function walkBoundary(start_node, end_node, next) {
7044
+ var siblingName = next ? 'nextSibling' : 'previousSibling';
7045
+
7046
+ for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7047
+ parent = node.parentNode;
7048
+ siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7049
+
7050
+ if (siblings.length) {
7051
+ if (!next) {
7052
+ siblings.reverse();
7053
+ }
7054
+
7055
+ callback(exclude(siblings));
7056
+ }
7057
+ }
7058
+ }
7059
+
7060
+ // If index based start position then resolve it
7061
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
7062
+ startContainer = startContainer.childNodes[startOffset];
7063
+ }
7064
+
7065
+ // If index based end position then resolve it
7066
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
7067
+ endContainer = getEndChild(endContainer, endOffset);
7068
+ }
7069
+
7070
+ // Same container
7071
+ if (startContainer == endContainer) {
7072
+ return callback(exclude([startContainer]));
7073
+ }
7074
+
7075
+ // Find common ancestor and end points
7076
+ ancestor = dom.findCommonAncestor(startContainer, endContainer);
7077
+
7078
+ // Process left side
7079
+ for (node = startContainer; node; node = node.parentNode) {
7080
+ if (node === endContainer) {
7081
+ return walkBoundary(startContainer, ancestor, true);
7082
+ }
7083
+
7084
+ if (node === ancestor) {
7085
+ break;
7086
+ }
7087
+ }
7088
+
7089
+ // Process right side
7090
+ for (node = endContainer; node; node = node.parentNode) {
7091
+ if (node === startContainer) {
7092
+ return walkBoundary(endContainer, ancestor);
7093
+ }
7094
+
7095
+ if (node === ancestor) {
7096
+ break;
7097
+ }
7098
+ }
7099
+
7100
+ // Find start/end point
7101
+ startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7102
+ endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7103
+
7104
+ // Walk left leaf
7105
+ walkBoundary(startContainer, startPoint, true);
7106
+
7107
+ // Walk the middle from start to end point
7108
+ siblings = collectSiblings(
7109
+ startPoint == startContainer ? startPoint : startPoint.nextSibling,
7110
+ 'nextSibling',
7111
+ endPoint == endContainer ? endPoint.nextSibling : endPoint
7112
+ );
7113
+
7114
+ if (siblings.length) {
7115
+ callback(exclude(siblings));
7116
+ }
7117
+
7118
+ // Walk right leaf
7119
+ walkBoundary(endContainer, endPoint);
7120
+ };
7121
+
7122
+ /**
7123
+ * Splits the specified range at it's start/end points.
7124
+ *
7125
+ * @private
7126
+ * @param {Range/RangeObject} rng Range to split.
7127
+ * @return {Object} Range position object.
7128
+ */
7129
+ this.split = function(rng) {
7130
+ var startContainer = rng.startContainer,
7131
+ startOffset = rng.startOffset,
7132
+ endContainer = rng.endContainer,
7133
+ endOffset = rng.endOffset;
7134
+
7135
+ function splitText(node, offset) {
7136
+ return node.splitText(offset);
7137
+ }
7138
+
7139
+ // Handle single text node
7140
+ if (startContainer == endContainer && startContainer.nodeType == 3) {
7141
+ if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7142
+ endContainer = splitText(startContainer, startOffset);
7143
+ startContainer = endContainer.previousSibling;
7144
+
7145
+ if (endOffset > startOffset) {
7146
+ endOffset = endOffset - startOffset;
7147
+ startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
7148
+ endOffset = endContainer.nodeValue.length;
7149
+ startOffset = 0;
7150
+ } else {
7151
+ endOffset = 0;
7152
+ }
7153
+ }
7154
+ } else {
7155
+ // Split startContainer text node if needed
7156
+ if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7157
+ startContainer = splitText(startContainer, startOffset);
7158
+ startOffset = 0;
7159
+ }
7160
+
7161
+ // Split endContainer text node if needed
7162
+ if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
7163
+ endContainer = splitText(endContainer, endOffset).previousSibling;
7164
+ endOffset = endContainer.nodeValue.length;
7165
+ }
7166
+ }
7167
+
7168
+ return {
7169
+ startContainer: startContainer,
7170
+ startOffset: startOffset,
7171
+ endContainer: endContainer,
7172
+ endOffset: endOffset
7173
+ };
7174
+ };
7175
+
7176
+ /**
7177
+ * Normalizes the specified range by finding the closest best suitable caret location.
7178
+ *
7179
+ * @private
7180
+ * @param {Range} rng Range to normalize.
7181
+ * @return {Boolean} True/false if the specified range was normalized or not.
7182
+ */
7183
+ this.normalize = function(rng) {
7184
+ var normalized, collapsed;
7185
+
7186
+ function normalizeEndPoint(start) {
7187
+ var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
7188
+ var directionLeft, isAfterNode;
7189
+
7190
+ function hasBrBeforeAfter(node, left) {
7191
+ var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
7192
+
7193
+ while ((node = walker[left ? 'prev' : 'next']())) {
7194
+ if (node.nodeName === "BR") {
7195
+ return true;
7196
+ }
7197
+ }
7198
+ }
7199
+
7200
+ function isPrevNode(node, name) {
7201
+ return node.previousSibling && node.previousSibling.nodeName == name;
7202
+ }
7203
+
7204
+ // Walks the dom left/right to find a suitable text node to move the endpoint into
7205
+ // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
7206
+ function findTextNodeRelative(left, startNode) {
7207
+ var walker, lastInlineElement, parentBlockContainer;
7208
+
7209
+ startNode = startNode || container;
7210
+ parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
7211
+
7212
+ // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
7213
+ // This: <p><br>|</p> becomes <p>|<br></p>
7214
+ if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
7215
+ container = startNode.parentNode;
7216
+ offset = dom.nodeIndex(startNode);
7217
+ normalized = true;
7218
+ return;
7219
+ }
7220
+
7221
+ // Walk left until we hit a text node we can move to or a block/br/img
7222
+ walker = new TreeWalker(startNode, parentBlockContainer);
7223
+ while ((node = walker[left ? 'prev' : 'next']())) {
7224
+ // Break if we hit a non content editable node
7225
+ if (dom.getContentEditableParent(node) === "false") {
7226
+ return;
7227
+ }
7228
+
7229
+ // Found text node that has a length
7230
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
7231
+ container = node;
7232
+ offset = left ? node.nodeValue.length : 0;
7233
+ normalized = true;
7234
+ return;
7235
+ }
7236
+
7237
+ // Break if we find a block or a BR/IMG/INPUT etc
7238
+ if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7239
+ return;
7240
+ }
7241
+
7242
+ lastInlineElement = node;
7243
+ }
7244
+
7245
+ // Only fetch the last inline element when in caret mode for now
7246
+ if (collapsed && lastInlineElement) {
7247
+ container = lastInlineElement;
7248
+ normalized = true;
7249
+ offset = 0;
7250
+ }
7251
+ }
7252
+
7253
+ container = rng[(start ? 'start' : 'end') + 'Container'];
7254
+ offset = rng[(start ? 'start' : 'end') + 'Offset'];
7255
+ isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
7256
+ nonEmptyElementsMap = dom.schema.getNonEmptyElements();
7257
+ directionLeft = start;
7258
+
7259
+ if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
7260
+ directionLeft = false;
7261
+ }
7262
+
7263
+ // If the container is a document move it to the body element
7264
+ if (container.nodeType === 9) {
7265
+ container = dom.getRoot();
7266
+ offset = 0;
7267
+ }
7268
+
7269
+ // If the container is body try move it into the closest text node or position
7270
+ if (container === body) {
7271
+ // If start is before/after a image, table etc
7272
+ if (directionLeft) {
7273
+ node = container.childNodes[offset > 0 ? offset - 1 : 0];
7274
+ if (node) {
7275
+ if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
7276
+ return;
7277
+ }
7278
+ }
7279
+ }
7280
+
7281
+ // Resolve the index
7282
+ if (container.hasChildNodes()) {
7283
+ offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
7284
+ container = container.childNodes[offset];
7285
+ offset = 0;
7286
+
7287
+ // Don't walk into elements that doesn't have any child nodes like a IMG
7288
+ if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
7289
+ // Walk the DOM to find a text node to place the caret at or a BR
7290
+ node = container;
7291
+ walker = new TreeWalker(container, body);
7292
+
7293
+ do {
7294
+ // Found a text node use that position
7295
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
7296
+ offset = directionLeft ? 0 : node.nodeValue.length;
7297
+ container = node;
7298
+ normalized = true;
7299
+ break;
7300
+ }
7301
+
7302
+ // Found a BR/IMG element that we can place the caret before
7303
+ if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7304
+ offset = dom.nodeIndex(node);
7305
+ container = node.parentNode;
7306
+
7307
+ // Put caret after image when moving the end point
7308
+ if (node.nodeName == "IMG" && !directionLeft) {
7309
+ offset++;
7310
+ }
7311
+
7312
+ normalized = true;
7313
+ break;
7314
+ }
7315
+ } while ((node = (directionLeft ? walker.next() : walker.prev())));
7316
+ }
7317
+ }
7318
+ }
7319
+
7320
+ // Lean the caret to the left if possible
7321
+ if (collapsed) {
7322
+ // So this: <b>x</b><i>|x</i>
7323
+ // Becomes: <b>x|</b><i>x</i>
7324
+ // Seems that only gecko has issues with this
7325
+ if (container.nodeType === 3 && offset === 0) {
7326
+ findTextNodeRelative(true);
7327
+ }
7328
+
7329
+ // Lean left into empty inline elements when the caret is before a BR
7330
+ // So this: <i><b></b><i>|<br></i>
7331
+ // Becomes: <i><b>|</b><i><br></i>
7332
+ // Seems that only gecko has issues with this.
7333
+ // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
7334
+ if (container.nodeType === 1) {
7335
+ node = container.childNodes[offset];
7336
+
7337
+ // Offset is after the containers last child
7338
+ // then use the previous child for normalization
7339
+ if (!node) {
7340
+ node = container.childNodes[offset - 1];
7341
+ }
7342
+
7343
+ if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
7344
+ !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
7345
+ findTextNodeRelative(true, node);
7346
+ }
7347
+ }
7348
+ }
7349
+
7350
+ // Lean the start of the selection right if possible
7351
+ // So this: x[<b>x]</b>
7352
+ // Becomes: x<b>[x]</b>
7353
+ if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
7354
+ findTextNodeRelative(false);
7355
+ }
7356
+
7357
+ // Set endpoint if it was normalized
7358
+ if (normalized) {
7359
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
7360
+ }
7361
+ }
7362
+
7363
+ collapsed = rng.collapsed;
7364
+
7365
+ normalizeEndPoint(true);
7366
+
7367
+ if (!collapsed) {
7368
+ normalizeEndPoint();
7369
+ }
7370
+
7371
+ // If it was collapsed then make sure it still is
7372
+ if (normalized && collapsed) {
7373
+ rng.collapse(true);
7374
+ }
7375
+
7376
+ return normalized;
7377
+ };
7378
+ }
7379
+
7380
+ /**
7381
+ * Compares two ranges and checks if they are equal.
7382
+ *
7383
+ * @static
7384
+ * @method compareRanges
7385
+ * @param {DOMRange} rng1 First range to compare.
7386
+ * @param {DOMRange} rng2 First range to compare.
7387
+ * @return {Boolean} true/false if the ranges are equal.
7388
+ */
7389
+ RangeUtils.compareRanges = function(rng1, rng2) {
7390
+ if (rng1 && rng2) {
7391
+ // Compare native IE ranges
7392
+ if (rng1.item || rng1.duplicate) {
7393
+ // Both are control ranges and the selected element matches
7394
+ if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
7395
+ return true;
7396
+ }
7397
+
7398
+ // Both are text ranges and the range matches
7399
+ if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
7400
+ return true;
7401
+ }
7402
+ } else {
7403
+ // Compare w3c ranges
7404
+ return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7405
+ }
7406
+ }
7407
+
7408
+ return false;
7409
+ };
7410
+
7411
+ return RangeUtils;
7412
+ });
7413
+
7414
+ // Included from: js/tinymce/classes/NodeChange.js
7415
+
7416
+ /**
7417
+ * NodeChange.js
7418
+ *
7419
+ * Copyright, Moxiecode Systems AB
7420
+ * Released under LGPL License.
7421
+ *
7422
+ * License: http://www.tinymce.com/license
7423
+ * Contributing: http://www.tinymce.com/contributing
7424
+ */
7425
+
7426
+ /**
7427
+ * This class handles the nodechange event dispatching both manual and though selection change events.
7428
+ *
7429
+ * @class tinymce.NodeChange
7430
+ * @private
7431
+ */
7432
+ define("tinymce/NodeChange", [
7433
+ "tinymce/dom/RangeUtils"
7434
+ ], function(RangeUtils) {
7435
+ return function(editor) {
7436
+ var lastRng, lastPath = [];
7437
+
7438
+ /**
7439
+ * Returns true/false if the current element path has been changed or not.
7440
+ *
7441
+ * @private
7442
+ * @return {Boolean} True if the element path is the same false if it's not.
7443
+ */
7444
+ function isSameElementPath(startElm) {
7445
+ var i, currentPath;
7446
+
7447
+ currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
7448
+ if (currentPath.length === lastPath.length) {
7449
+ for (i = currentPath.length; i >= 0; i--) {
7450
+ if (currentPath[i] !== lastPath[i]) {
7451
+ break;
7452
+ }
7453
+ }
7454
+
7455
+ if (i === -1) {
7456
+ lastPath = currentPath;
7457
+ return true;
7458
+ }
7459
+ }
7460
+
7461
+ lastPath = currentPath;
7462
+
7463
+ return false;
7464
+ }
7465
+
7466
+ // Gecko doesn't support the "selectionchange" event
7467
+ if (!('onselectionchange' in editor.getDoc())) {
7468
+ editor.on('NodeChange Click MouseUp KeyUp', function(e) {
7469
+ var nativeRng, fakeRng;
7470
+
7471
+ // Since DOM Ranges mutate on modification
7472
+ // of the DOM we need to clone it's contents
7473
+ nativeRng = editor.selection.getRng();
7474
+ fakeRng = {
7475
+ startContainer: nativeRng.startContainer,
7476
+ startOffset: nativeRng.startOffset,
7477
+ endContainer: nativeRng.endContainer,
7478
+ endOffset: nativeRng.endOffset
7479
+ };
7480
+
7481
+ // Always treat nodechange as a selectionchange since applying
7482
+ // formatting to the current range wouldn't update the range but it's parent
7483
+ if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
7484
+ editor.fire('SelectionChange');
7485
+ }
7486
+
7487
+ lastRng = fakeRng;
7488
+ });
7489
+ }
7490
+
7491
+ // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
7492
+ // When the contextmenu event fires the selection is located at the right location
7493
+ editor.on('contextmenu', function() {
7494
+ editor.fire('SelectionChange');
7495
+ });
7496
+
7497
+ editor.on('SelectionChange', function() {
7498
+ var startElm = editor.selection.getStart();
7499
+
7500
+ // Selection change might fire when focus is lost so check if the start is still within the body
7501
+ if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
7502
+ editor.nodeChanged({selectionChange: true});
7503
+ }
7504
+ });
7505
+
7506
+ /**
7507
+ * Distpaches out a onNodeChange event to all observers. This method should be called when you
7508
+ * need to update the UI states or element path etc.
7509
+ *
7510
+ * @method nodeChanged
7511
+ * @param {Object} args Optional args to pass to NodeChange event handlers.
7512
+ */
7513
+ this.nodeChanged = function(args) {
7514
+ var selection = editor.selection, node, parents, root;
7515
+
7516
+ // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
7517
+ if (editor.initialized && !editor.settings.disable_nodechange && !editor.settings.readonly) {
7518
+ // Get start node
7519
+ root = editor.getBody();
7520
+ node = selection.getStart() || root;
7521
+ node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;
7522
+
7523
+ // Edge case for <p>|<img></p>
7524
+ if (node.nodeName == 'IMG' && selection.isCollapsed()) {
7525
+ node = node.parentNode;
7526
+ }
7527
+
7528
+ // Get parents and add them to object
7529
+ parents = [];
7530
+ editor.dom.getParent(node, function(node) {
7531
+ if (node === root) {
7532
+ return true;
7533
+ }
7534
+
7535
+ parents.push(node);
7536
+ });
7537
+
7538
+ args = args || {};
7539
+ args.element = node;
7540
+ args.parents = parents;
7541
+
7542
+ editor.fire('NodeChange', args);
7543
+ }
7544
+ };
7545
+ };
7546
+ });
7547
+
7548
+ // Included from: js/tinymce/classes/html/Node.js
7549
+
7550
+ /**
7551
+ * Node.js
7552
+ *
7553
+ * Copyright, Moxiecode Systems AB
7554
+ * Released under LGPL License.
7555
+ *
7556
+ * License: http://www.tinymce.com/license
7557
+ * Contributing: http://www.tinymce.com/contributing
7558
+ */
7559
+
7560
+ /**
7561
+ * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
7562
+ *
7563
+ * @example
7564
+ * var node = new tinymce.html.Node('strong', 1);
7565
+ * someRoot.append(node);
7566
+ *
7567
+ * @class tinymce.html.Node
7568
+ * @version 3.4
7569
+ */
7570
+ define("tinymce/html/Node", [], function() {
7571
+ var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
7572
+ '#text': 3,
7573
+ '#comment': 8,
7574
+ '#cdata': 4,
7575
+ '#pi': 7,
7576
+ '#doctype': 10,
7577
+ '#document-fragment': 11
7578
+ };
7579
+
7580
+ // Walks the tree left/right
7581
+ function walk(node, root_node, prev) {
7582
+ var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
7583
+
7584
+ // Walk into nodes if it has a start
7585
+ if (node[startName]) {
7586
+ return node[startName];
7587
+ }
7588
+
7589
+ // Return the sibling if it has one
7590
+ if (node !== root_node) {
7591
+ sibling = node[siblingName];
7592
+
7593
+ if (sibling) {
7594
+ return sibling;
7595
+ }
7596
+
7597
+ // Walk up the parents to look for siblings
7598
+ for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
7599
+ sibling = parent[siblingName];
7600
+
7601
+ if (sibling) {
7602
+ return sibling;
7603
+ }
7604
+ }
7605
+ }
7606
+ }
7607
+
7608
+ /**
7609
+ * Constructs a new Node instance.
7610
+ *
7611
+ * @constructor
7612
+ * @method Node
7613
+ * @param {String} name Name of the node type.
7614
+ * @param {Number} type Numeric type representing the node.
7615
+ */
7616
+ function Node(name, type) {
7617
+ this.name = name;
7618
+ this.type = type;
7619
+
7620
+ if (type === 1) {
7621
+ this.attributes = [];
7622
+ this.attributes.map = {};
7623
+ }
7624
+ }
7625
+
7626
+ Node.prototype = {
7627
+ /**
7628
+ * Replaces the current node with the specified one.
7629
+ *
7630
+ * @example
7631
+ * someNode.replace(someNewNode);
7632
+ *
7633
+ * @method replace
7634
+ * @param {tinymce.html.Node} node Node to replace the current node with.
7635
+ * @return {tinymce.html.Node} The old node that got replaced.
7636
+ */
7637
+ replace: function(node) {
7638
+ var self = this;
7639
+
7640
+ if (node.parent) {
7641
+ node.remove();
7642
+ }
7093
7643
 
7094
7644
  self.insert(node, self);
7095
7645
  self.remove();
@@ -7907,16 +8457,16 @@ define("tinymce/html/Schema", [
7907
8457
 
7908
8458
  // Parses the specified valid_elements string and adds to the current rules
7909
8459
  // This function is a bit hard to read since it's heavily optimized for speed
7910
- function addValidElements(valid_elements) {
8460
+ function addValidElements(validElements) {
7911
8461
  var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
7912
8462
  prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
7913
8463
  elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
7914
8464
  attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
7915
8465
  hasPatternsRegExp = /[*?+]/;
7916
8466
 
7917
- if (valid_elements) {
8467
+ if (validElements) {
7918
8468
  // Split valid elements into an array with rules
7919
- valid_elements = split(valid_elements, ',');
8469
+ validElements = split(validElements, ',');
7920
8470
 
7921
8471
  if (elements['@']) {
7922
8472
  globalAttributes = elements['@'].attributes;
@@ -7924,9 +8474,9 @@ define("tinymce/html/Schema", [
7924
8474
  }
7925
8475
 
7926
8476
  // Loop all rules
7927
- for (ei = 0, el = valid_elements.length; ei < el; ei++) {
8477
+ for (ei = 0, el = validElements.length; ei < el; ei++) {
7928
8478
  // Parse element rule
7929
- matches = elementRuleRegExp.exec(valid_elements[ei]);
8479
+ matches = elementRuleRegExp.exec(validElements[ei]);
7930
8480
  if (matches) {
7931
8481
  // Setup local names for matches
7932
8482
  prefix = matches[1];
@@ -8056,11 +8606,11 @@ define("tinymce/html/Schema", [
8056
8606
  }
8057
8607
  }
8058
8608
 
8059
- function setValidElements(valid_elements) {
8609
+ function setValidElements(validElements) {
8060
8610
  elements = {};
8061
8611
  patternElements = [];
8062
8612
 
8063
- addValidElements(valid_elements);
8613
+ addValidElements(validElements);
8064
8614
 
8065
8615
  each(schemaItems, function(element, name) {
8066
8616
  children[name] = element.children;
@@ -8068,14 +8618,14 @@ define("tinymce/html/Schema", [
8068
8618
  }
8069
8619
 
8070
8620
  // Adds custom non HTML elements to the schema
8071
- function addCustomElements(custom_elements) {
8621
+ function addCustomElements(customElements) {
8072
8622
  var customElementRegExp = /^(~)?(.+)$/;
8073
8623
 
8074
- if (custom_elements) {
8624
+ if (customElements) {
8075
8625
  // Flush cached items since we are altering the default maps
8076
8626
  mapCache.text_block_elements = mapCache.block_elements = null;
8077
8627
 
8078
- each(split(custom_elements, ','), function(rule) {
8628
+ each(split(customElements, ','), function(rule) {
8079
8629
  var matches = customElementRegExp.exec(rule),
8080
8630
  inline = matches[1] === '~',
8081
8631
  cloneName = inline ? 'span' : 'div',
@@ -8113,11 +8663,11 @@ define("tinymce/html/Schema", [
8113
8663
  }
8114
8664
 
8115
8665
  // Adds valid children to the schema object
8116
- function addValidChildren(valid_children) {
8666
+ function addValidChildren(validChildren) {
8117
8667
  var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
8118
8668
 
8119
- if (valid_children) {
8120
- each(split(valid_children, ','), function(rule) {
8669
+ if (validChildren) {
8670
+ each(split(validChildren, ','), function(rule) {
8121
8671
  var matches = childRuleRegExp.exec(rule), parent, prefix;
8122
8672
 
8123
8673
  if (matches) {
@@ -8562,7 +9112,7 @@ define("tinymce/html/SaxParser", [
8562
9112
  var count = 1, index, matches, tokenRegExp, shortEndedElements;
8563
9113
 
8564
9114
  shortEndedElements = schema.getShortEndedElements();
8565
- tokenRegExp = /<([!?\/])?([A-Za-z0-9\-\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
9115
+ tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
8566
9116
  tokenRegExp.lastIndex = index = startIndex;
8567
9117
 
8568
9118
  while ((matches = tokenRegExp.exec(html))) {
@@ -8730,7 +9280,7 @@ define("tinymce/html/SaxParser", [
8730
9280
  '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
8731
9281
  '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
8732
9282
  '(?:\\/([^>]+)>)|' + // End element
8733
- '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
9283
+ '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
8734
9284
  ')', 'g');
8735
9285
 
8736
9286
  attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
@@ -11338,7 +11888,11 @@ define("tinymce/dom/ControlSelection", [
11338
11888
  ratio = startH / startW;
11339
11889
  selectedHandle = handle;
11340
11890
 
11341
- handle.startPos = dom.getPos(handle.elm, rootElement);
11891
+ handle.startPos = {
11892
+ x: targetWidth * handle[0] + selectedElmX,
11893
+ y: targetHeight * handle[1] + selectedElmY
11894
+ };
11895
+
11342
11896
  startScrollWidth = rootElement.scrollWidth;
11343
11897
  startScrollHeight = rootElement.scrollHeight;
11344
11898
 
@@ -11461,7 +12015,7 @@ define("tinymce/dom/ControlSelection", [
11461
12015
  });
11462
12016
 
11463
12017
  controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
11464
- controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
12018
+ controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
11465
12019
 
11466
12020
  if (isChildOrEqual(controlElm, rootElement)) {
11467
12021
  disableGeckoResize();
@@ -11511,6 +12065,11 @@ define("tinymce/dom/ControlSelection", [
11511
12065
 
11512
12066
  // Remove native selection and let the magic begin
11513
12067
  resizeStarted = true;
12068
+ editor.fire('ObjectResizeStart', {
12069
+ target: selectedElm,
12070
+ width: selectedElm.clientWidth,
12071
+ height: selectedElm.clientHeight
12072
+ });
11514
12073
  editor.getDoc().selection.empty();
11515
12074
  showResizeRect(target, name, lastMouseDownEvent);
11516
12075
  }
@@ -11519,6 +12078,7 @@ define("tinymce/dom/ControlSelection", [
11519
12078
  var target = e.srcElement;
11520
12079
 
11521
12080
  if (target != selectedElm) {
12081
+ editor.fire('ObjectSelected', {target: target});
11522
12082
  detachResizeStartListener();
11523
12083
 
11524
12084
  if (target.id.indexOf('mceResizeHandle') === 0) {
@@ -11599,7 +12159,7 @@ define("tinymce/dom/ControlSelection", [
11599
12159
  editor.on('mouseup', function(e) {
11600
12160
  var nodeName = e.target.nodeName;
11601
12161
 
11602
- if (/^(TABLE|IMG|HR)$/.test(nodeName)) {
12162
+ if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
11603
12163
  editor.selection.select(e.target, nodeName == 'TABLE');
11604
12164
  editor.nodeChanged();
11605
12165
  }
@@ -11658,10 +12218,10 @@ define("tinymce/dom/ControlSelection", [
11658
12218
  };
11659
12219
  });
11660
12220
 
11661
- // Included from: js/tinymce/classes/dom/RangeUtils.js
12221
+ // Included from: js/tinymce/classes/dom/BookmarkManager.js
11662
12222
 
11663
12223
  /**
11664
- * RangeUtils.js
12224
+ * BookmarkManager.js
11665
12225
  *
11666
12226
  * Copyright, Moxiecode Systems AB
11667
12227
  * Released under LGPL License.
@@ -11671,880 +12231,383 @@ define("tinymce/dom/ControlSelection", [
11671
12231
  */
11672
12232
 
11673
12233
  /**
11674
- * This class contains a few utility methods for ranges.
12234
+ * This class handles selection bookmarks.
11675
12235
  *
11676
- * @class tinymce.dom.RangeUtils
11677
- * @private
12236
+ * @class tinymce.dom.BookmarkManager
11678
12237
  */
11679
- define("tinymce/dom/RangeUtils", [
11680
- "tinymce/util/Tools",
11681
- "tinymce/dom/TreeWalker"
11682
- ], function(Tools, TreeWalker) {
11683
- var each = Tools.each;
11684
-
11685
- function getEndChild(container, index) {
11686
- var childNodes = container.childNodes;
11687
-
11688
- index--;
11689
-
11690
- if (index > childNodes.length - 1) {
11691
- index = childNodes.length - 1;
11692
- } else if (index < 0) {
11693
- index = 0;
11694
- }
11695
-
11696
- return childNodes[index] || container;
11697
- }
12238
+ define("tinymce/dom/BookmarkManager", [
12239
+ "tinymce/Env",
12240
+ "tinymce/util/Tools"
12241
+ ], function(Env, Tools) {
12242
+ /**
12243
+ * Constructs a new BookmarkManager instance for a specific selection instance.
12244
+ *
12245
+ * @constructor
12246
+ * @method BookmarkManager
12247
+ * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
12248
+ */
12249
+ function BookmarkManager(selection) {
12250
+ var dom = selection.dom;
11698
12251
 
11699
- function RangeUtils(dom) {
11700
12252
  /**
11701
- * Walks the specified range like object and executes the callback for each sibling collection it finds.
12253
+ * Returns a bookmark location for the current selection. This bookmark object
12254
+ * can then be used to restore the selection after some content modification to the document.
11702
12255
  *
11703
- * @method walk
11704
- * @param {Object} rng Range like object.
11705
- * @param {function} callback Callback function to execute for each sibling collection.
12256
+ * @method getBookmark
12257
+ * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
12258
+ * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
12259
+ * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
12260
+ * @example
12261
+ * // Stores a bookmark of the current selection
12262
+ * var bm = tinymce.activeEditor.selection.getBookmark();
12263
+ *
12264
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12265
+ *
12266
+ * // Restore the selection bookmark
12267
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
11706
12268
  */
11707
- this.walk = function(rng, callback) {
11708
- var startContainer = rng.startContainer,
11709
- startOffset = rng.startOffset,
11710
- endContainer = rng.endContainer,
11711
- endOffset = rng.endOffset,
11712
- ancestor, startPoint,
11713
- endPoint, node, parent, siblings, nodes;
11714
-
11715
- // Handle table cell selection the table plugin enables
11716
- // you to fake select table cells and perform formatting actions on them
11717
- nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
11718
- if (nodes.length > 0) {
11719
- each(nodes, function(node) {
11720
- callback([node]);
11721
- });
11722
-
11723
- return;
11724
- }
12269
+ this.getBookmark = function(type, normalized) {
12270
+ var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
11725
12271
 
11726
- /**
11727
- * Excludes start/end text node if they are out side the range
11728
- *
11729
- * @private
11730
- * @param {Array} nodes Nodes to exclude items from.
11731
- * @return {Array} Array with nodes excluding the start/end container if needed.
11732
- */
11733
- function exclude(nodes) {
11734
- var node;
11735
-
11736
- // First node is excluded
11737
- node = nodes[0];
11738
- if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
11739
- nodes.splice(0, 1);
11740
- }
11741
-
11742
- // Last node is excluded
11743
- node = nodes[nodes.length - 1];
11744
- if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
11745
- nodes.splice(nodes.length - 1, 1);
11746
- }
11747
-
11748
- return nodes;
11749
- }
11750
-
11751
- /**
11752
- * Collects siblings
11753
- *
11754
- * @private
11755
- * @param {Node} node Node to collect siblings from.
11756
- * @param {String} name Name of the sibling to check for.
11757
- * @return {Array} Array of collected siblings.
11758
- */
11759
- function collectSiblings(node, name, end_node) {
11760
- var siblings = [];
11761
-
11762
- for (; node && node != end_node; node = node[name]) {
11763
- siblings.push(node);
11764
- }
11765
-
11766
- return siblings;
11767
- }
12272
+ function findIndex(name, element) {
12273
+ var index = 0;
11768
12274
 
11769
- /**
11770
- * Find an end point this is the node just before the common ancestor root.
11771
- *
11772
- * @private
11773
- * @param {Node} node Node to start at.
11774
- * @param {Node} root Root/ancestor element to stop just before.
11775
- * @return {Node} Node just before the root element.
11776
- */
11777
- function findEndPoint(node, root) {
11778
- do {
11779
- if (node.parentNode == root) {
11780
- return node;
12275
+ Tools.each(dom.select(name), function(node, i) {
12276
+ if (node == element) {
12277
+ index = i;
11781
12278
  }
12279
+ });
11782
12280
 
11783
- node = node.parentNode;
11784
- } while (node);
12281
+ return index;
11785
12282
  }
11786
12283
 
11787
- function walkBoundary(start_node, end_node, next) {
11788
- var siblingName = next ? 'nextSibling' : 'previousSibling';
11789
-
11790
- for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
11791
- parent = node.parentNode;
11792
- siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
11793
-
11794
- if (siblings.length) {
11795
- if (!next) {
11796
- siblings.reverse();
11797
- }
12284
+ function normalizeTableCellSelection(rng) {
12285
+ function moveEndPoint(start) {
12286
+ var container, offset, childNodes, prefix = start ? 'start' : 'end';
11798
12287
 
11799
- callback(exclude(siblings));
12288
+ container = rng[prefix + 'Container'];
12289
+ offset = rng[prefix + 'Offset'];
12290
+
12291
+ if (container.nodeType == 1 && container.nodeName == "TR") {
12292
+ childNodes = container.childNodes;
12293
+ container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
12294
+ if (container) {
12295
+ offset = start ? 0 : container.childNodes.length;
12296
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
12297
+ }
11800
12298
  }
11801
12299
  }
11802
- }
11803
12300
 
11804
- // If index based start position then resolve it
11805
- if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
11806
- startContainer = startContainer.childNodes[startOffset];
11807
- }
12301
+ moveEndPoint(true);
12302
+ moveEndPoint();
11808
12303
 
11809
- // If index based end position then resolve it
11810
- if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
11811
- endContainer = getEndChild(endContainer, endOffset);
12304
+ return rng;
11812
12305
  }
11813
12306
 
11814
- // Same container
11815
- if (startContainer == endContainer) {
11816
- return callback(exclude([startContainer]));
11817
- }
12307
+ function getLocation() {
12308
+ var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
11818
12309
 
11819
- // Find common ancestor and end points
11820
- ancestor = dom.findCommonAncestor(startContainer, endContainer);
12310
+ function getPoint(rng, start) {
12311
+ var container = rng[start ? 'startContainer' : 'endContainer'],
12312
+ offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
11821
12313
 
11822
- // Process left side
11823
- for (node = startContainer; node; node = node.parentNode) {
11824
- if (node === endContainer) {
11825
- return walkBoundary(startContainer, ancestor, true);
11826
- }
12314
+ if (container.nodeType == 3) {
12315
+ if (normalized) {
12316
+ for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
12317
+ offset += node.nodeValue.length;
12318
+ }
12319
+ }
11827
12320
 
11828
- if (node === ancestor) {
11829
- break;
11830
- }
11831
- }
12321
+ point.push(offset);
12322
+ } else {
12323
+ childNodes = container.childNodes;
11832
12324
 
11833
- // Process right side
11834
- for (node = endContainer; node; node = node.parentNode) {
11835
- if (node === startContainer) {
11836
- return walkBoundary(endContainer, ancestor);
12325
+ if (offset >= childNodes.length && childNodes.length) {
12326
+ after = 1;
12327
+ offset = Math.max(0, childNodes.length - 1);
12328
+ }
12329
+
12330
+ point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
12331
+ }
12332
+
12333
+ for (; container && container != root; container = container.parentNode) {
12334
+ point.push(dom.nodeIndex(container, normalized));
12335
+ }
12336
+
12337
+ return point;
11837
12338
  }
11838
12339
 
11839
- if (node === ancestor) {
11840
- break;
12340
+ bookmark.start = getPoint(rng, true);
12341
+
12342
+ if (!selection.isCollapsed()) {
12343
+ bookmark.end = getPoint(rng);
11841
12344
  }
12345
+
12346
+ return bookmark;
11842
12347
  }
11843
12348
 
11844
- // Find start/end point
11845
- startPoint = findEndPoint(startContainer, ancestor) || startContainer;
11846
- endPoint = findEndPoint(endContainer, ancestor) || endContainer;
12349
+ if (type == 2) {
12350
+ element = selection.getNode();
12351
+ name = element ? element.nodeName : null;
11847
12352
 
11848
- // Walk left leaf
11849
- walkBoundary(startContainer, startPoint, true);
12353
+ if (name == 'IMG') {
12354
+ return {name: name, index: findIndex(name, element)};
12355
+ }
11850
12356
 
11851
- // Walk the middle from start to end point
11852
- siblings = collectSiblings(
11853
- startPoint == startContainer ? startPoint : startPoint.nextSibling,
11854
- 'nextSibling',
11855
- endPoint == endContainer ? endPoint.nextSibling : endPoint
11856
- );
12357
+ if (selection.tridentSel) {
12358
+ return selection.tridentSel.getBookmark(type);
12359
+ }
11857
12360
 
11858
- if (siblings.length) {
11859
- callback(exclude(siblings));
12361
+ return getLocation();
11860
12362
  }
11861
12363
 
11862
- // Walk right leaf
11863
- walkBoundary(endContainer, endPoint);
11864
- };
12364
+ // Handle simple range
12365
+ if (type) {
12366
+ return {rng: selection.getRng()};
12367
+ }
11865
12368
 
11866
- /**
11867
- * Splits the specified range at it's start/end points.
11868
- *
11869
- * @private
11870
- * @param {Range/RangeObject} rng Range to split.
11871
- * @return {Object} Range position object.
11872
- */
11873
- this.split = function(rng) {
11874
- var startContainer = rng.startContainer,
11875
- startOffset = rng.startOffset,
11876
- endContainer = rng.endContainer,
11877
- endOffset = rng.endOffset;
12369
+ rng = selection.getRng();
12370
+ id = dom.uniqueId();
12371
+ collapsed = selection.isCollapsed();
12372
+ styles = 'overflow:hidden;line-height:0px';
11878
12373
 
11879
- function splitText(node, offset) {
11880
- return node.splitText(offset);
11881
- }
12374
+ // Explorer method
12375
+ if (rng.duplicate || rng.item) {
12376
+ // Text selection
12377
+ if (!rng.item) {
12378
+ rng2 = rng.duplicate();
11882
12379
 
11883
- // Handle single text node
11884
- if (startContainer == endContainer && startContainer.nodeType == 3) {
11885
- if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11886
- endContainer = splitText(startContainer, startOffset);
11887
- startContainer = endContainer.previousSibling;
12380
+ try {
12381
+ // Insert start marker
12382
+ rng.collapse();
12383
+ rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
12384
+
12385
+ // Insert end marker
12386
+ if (!collapsed) {
12387
+ rng2.collapse(false);
12388
+
12389
+ // Detect the empty space after block elements in IE and move the
12390
+ // end back one character <p></p>] becomes <p>]</p>
12391
+ rng.moveToElementText(rng2.parentElement());
12392
+ if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
12393
+ rng2.move('character', -1);
12394
+ }
11888
12395
 
11889
- if (endOffset > startOffset) {
11890
- endOffset = endOffset - startOffset;
11891
- startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
11892
- endOffset = endContainer.nodeValue.length;
11893
- startOffset = 0;
11894
- } else {
11895
- endOffset = 0;
12396
+ rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
12397
+ }
12398
+ } catch (ex) {
12399
+ // IE might throw unspecified error so lets ignore it
12400
+ return null;
11896
12401
  }
12402
+ } else {
12403
+ // Control selection
12404
+ element = rng.item(0);
12405
+ name = element.nodeName;
12406
+
12407
+ return {name: name, index: findIndex(name, element)};
11897
12408
  }
11898
12409
  } else {
11899
- // Split startContainer text node if needed
11900
- if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11901
- startContainer = splitText(startContainer, startOffset);
11902
- startOffset = 0;
12410
+ element = selection.getNode();
12411
+ name = element.nodeName;
12412
+ if (name == 'IMG') {
12413
+ return {name: name, index: findIndex(name, element)};
11903
12414
  }
11904
12415
 
11905
- // Split endContainer text node if needed
11906
- if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
11907
- endContainer = splitText(endContainer, endOffset).previousSibling;
11908
- endOffset = endContainer.nodeValue.length;
12416
+ // W3C method
12417
+ rng2 = normalizeTableCellSelection(rng.cloneRange());
12418
+
12419
+ // Insert end marker
12420
+ if (!collapsed) {
12421
+ rng2.collapse(false);
12422
+ rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
11909
12423
  }
12424
+
12425
+ rng = normalizeTableCellSelection(rng);
12426
+ rng.collapse(true);
12427
+ rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
11910
12428
  }
11911
12429
 
11912
- return {
11913
- startContainer: startContainer,
11914
- startOffset: startOffset,
11915
- endContainer: endContainer,
11916
- endOffset: endOffset
11917
- };
12430
+ selection.moveToBookmark({id: id, keep: 1});
12431
+
12432
+ return {id: id};
11918
12433
  };
11919
12434
 
11920
12435
  /**
11921
- * Normalizes the specified range by finding the closest best suitable caret location.
12436
+ * Restores the selection to the specified bookmark.
11922
12437
  *
11923
- * @private
11924
- * @param {Range} rng Range to normalize.
11925
- * @return {Boolean} True/false if the specified range was normalized or not.
12438
+ * @method moveToBookmark
12439
+ * @param {Object} bookmark Bookmark to restore selection from.
12440
+ * @return {Boolean} true/false if it was successful or not.
12441
+ * @example
12442
+ * // Stores a bookmark of the current selection
12443
+ * var bm = tinymce.activeEditor.selection.getBookmark();
12444
+ *
12445
+ * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12446
+ *
12447
+ * // Restore the selection bookmark
12448
+ * tinymce.activeEditor.selection.moveToBookmark(bm);
11926
12449
  */
11927
- this.normalize = function(rng) {
11928
- var normalized, collapsed;
11929
-
11930
- function normalizeEndPoint(start) {
11931
- var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
11932
- var directionLeft, isAfterNode;
11933
-
11934
- function hasBrBeforeAfter(node, left) {
11935
- var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
11936
-
11937
- while ((node = walker[left ? 'prev' : 'next']())) {
11938
- if (node.nodeName === "BR") {
11939
- return true;
11940
- }
11941
- }
11942
- }
11943
-
11944
- function isPrevNode(node, name) {
11945
- return node.previousSibling && node.previousSibling.nodeName == name;
11946
- }
11947
-
11948
- // Walks the dom left/right to find a suitable text node to move the endpoint into
11949
- // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
11950
- function findTextNodeRelative(left, startNode) {
11951
- var walker, lastInlineElement, parentBlockContainer;
11952
-
11953
- startNode = startNode || container;
11954
- parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
12450
+ this.moveToBookmark = function(bookmark) {
12451
+ var rng, root, startContainer, endContainer, startOffset, endOffset;
11955
12452
 
11956
- // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
11957
- // This: <p><br>|</p> becomes <p>|<br></p>
11958
- if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
11959
- container = startNode.parentNode;
11960
- offset = dom.nodeIndex(startNode);
11961
- normalized = true;
11962
- return;
11963
- }
12453
+ function setEndPoint(start) {
12454
+ var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
11964
12455
 
11965
- // Walk left until we hit a text node we can move to or a block/br/img
11966
- walker = new TreeWalker(startNode, parentBlockContainer);
11967
- while ((node = walker[left ? 'prev' : 'next']())) {
11968
- // Break if we hit a non content editable node
11969
- if (dom.getContentEditableParent(node) === "false") {
11970
- return;
11971
- }
12456
+ if (point) {
12457
+ offset = point[0];
11972
12458
 
11973
- // Found text node that has a length
11974
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
11975
- container = node;
11976
- offset = left ? node.nodeValue.length : 0;
11977
- normalized = true;
11978
- return;
11979
- }
12459
+ // Find container node
12460
+ for (node = root, i = point.length - 1; i >= 1; i--) {
12461
+ children = node.childNodes;
11980
12462
 
11981
- // Break if we find a block or a BR/IMG/INPUT etc
11982
- if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12463
+ if (point[i] > children.length - 1) {
11983
12464
  return;
11984
12465
  }
11985
12466
 
11986
- lastInlineElement = node;
12467
+ node = children[point[i]];
11987
12468
  }
11988
12469
 
11989
- // Only fetch the last inline element when in caret mode for now
11990
- if (collapsed && lastInlineElement) {
11991
- container = lastInlineElement;
11992
- normalized = true;
11993
- offset = 0;
12470
+ // Move text offset to best suitable location
12471
+ if (node.nodeType === 3) {
12472
+ offset = Math.min(point[0], node.nodeValue.length);
11994
12473
  }
11995
- }
11996
-
11997
- container = rng[(start ? 'start' : 'end') + 'Container'];
11998
- offset = rng[(start ? 'start' : 'end') + 'Offset'];
11999
- isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
12000
- nonEmptyElementsMap = dom.schema.getNonEmptyElements();
12001
- directionLeft = start;
12002
-
12003
- if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
12004
- directionLeft = false;
12005
- }
12006
12474
 
12007
- // If the container is a document move it to the body element
12008
- if (container.nodeType === 9) {
12009
- container = dom.getRoot();
12010
- offset = 0;
12011
- }
12012
-
12013
- // If the container is body try move it into the closest text node or position
12014
- if (container === body) {
12015
- // If start is before/after a image, table etc
12016
- if (directionLeft) {
12017
- node = container.childNodes[offset > 0 ? offset - 1 : 0];
12018
- if (node) {
12019
- if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
12020
- return;
12021
- }
12022
- }
12475
+ // Move element offset to best suitable location
12476
+ if (node.nodeType === 1) {
12477
+ offset = Math.min(point[0], node.childNodes.length);
12023
12478
  }
12024
12479
 
12025
- // Resolve the index
12026
- if (container.hasChildNodes()) {
12027
- offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
12028
- container = container.childNodes[offset];
12029
- offset = 0;
12030
-
12031
- // Don't walk into elements that doesn't have any child nodes like a IMG
12032
- if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
12033
- // Walk the DOM to find a text node to place the caret at or a BR
12034
- node = container;
12035
- walker = new TreeWalker(container, body);
12036
-
12037
- do {
12038
- // Found a text node use that position
12039
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
12040
- offset = directionLeft ? 0 : node.nodeValue.length;
12041
- container = node;
12042
- normalized = true;
12043
- break;
12044
- }
12480
+ // Set offset within container node
12481
+ if (start) {
12482
+ rng.setStart(node, offset);
12483
+ } else {
12484
+ rng.setEnd(node, offset);
12485
+ }
12486
+ }
12045
12487
 
12046
- // Found a BR/IMG element that we can place the caret before
12047
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12048
- offset = dom.nodeIndex(node);
12049
- container = node.parentNode;
12488
+ return true;
12489
+ }
12050
12490
 
12051
- // Put caret after image when moving the end point
12052
- if (node.nodeName == "IMG" && !directionLeft) {
12053
- offset++;
12054
- }
12491
+ function restoreEndPoint(suffix) {
12492
+ var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
12055
12493
 
12056
- normalized = true;
12057
- break;
12058
- }
12059
- } while ((node = (directionLeft ? walker.next() : walker.prev())));
12494
+ if (marker) {
12495
+ node = marker.parentNode;
12496
+
12497
+ if (suffix == 'start') {
12498
+ if (!keep) {
12499
+ idx = dom.nodeIndex(marker);
12500
+ } else {
12501
+ node = marker.firstChild;
12502
+ idx = 1;
12060
12503
  }
12061
- }
12062
- }
12063
12504
 
12064
- // Lean the caret to the left if possible
12065
- if (collapsed) {
12066
- // So this: <b>x</b><i>|x</i>
12067
- // Becomes: <b>x|</b><i>x</i>
12068
- // Seems that only gecko has issues with this
12069
- if (container.nodeType === 3 && offset === 0) {
12070
- findTextNodeRelative(true);
12505
+ startContainer = endContainer = node;
12506
+ startOffset = endOffset = idx;
12507
+ } else {
12508
+ if (!keep) {
12509
+ idx = dom.nodeIndex(marker);
12510
+ } else {
12511
+ node = marker.firstChild;
12512
+ idx = 1;
12513
+ }
12514
+
12515
+ endContainer = node;
12516
+ endOffset = idx;
12071
12517
  }
12072
12518
 
12073
- // Lean left into empty inline elements when the caret is before a BR
12074
- // So this: <i><b></b><i>|<br></i>
12075
- // Becomes: <i><b>|</b><i><br></i>
12076
- // Seems that only gecko has issues with this.
12077
- // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
12078
- if (container.nodeType === 1) {
12079
- node = container.childNodes[offset];
12519
+ if (!keep) {
12520
+ prev = marker.previousSibling;
12521
+ next = marker.nextSibling;
12080
12522
 
12081
- // Offset is after the containers last child
12082
- // then use the previous child for normalization
12083
- if (!node) {
12084
- node = container.childNodes[offset - 1];
12523
+ // Remove all marker text nodes
12524
+ Tools.each(Tools.grep(marker.childNodes), function(node) {
12525
+ if (node.nodeType == 3) {
12526
+ node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
12527
+ }
12528
+ });
12529
+
12530
+ // Remove marker but keep children if for example contents where inserted into the marker
12531
+ // Also remove duplicated instances of the marker for example by a
12532
+ // split operation or by WebKit auto split on paste feature
12533
+ while ((marker = dom.get(bookmark.id + '_' + suffix))) {
12534
+ dom.remove(marker, 1);
12085
12535
  }
12086
12536
 
12087
- if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
12088
- !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
12089
- findTextNodeRelative(true, node);
12537
+ // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
12538
+ // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
12539
+ // isn't worth the effort. Sorry, Opera but it's just a fact
12540
+ if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
12541
+ idx = prev.nodeValue.length;
12542
+ prev.appendData(next.nodeValue);
12543
+ dom.remove(next);
12544
+
12545
+ if (suffix == 'start') {
12546
+ startContainer = endContainer = prev;
12547
+ startOffset = endOffset = idx;
12548
+ } else {
12549
+ endContainer = prev;
12550
+ endOffset = idx;
12551
+ }
12090
12552
  }
12091
12553
  }
12092
12554
  }
12555
+ }
12093
12556
 
12094
- // Lean the start of the selection right if possible
12095
- // So this: x[<b>x]</b>
12096
- // Becomes: x<b>[x]</b>
12097
- if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
12098
- findTextNodeRelative(false);
12557
+ function addBogus(node) {
12558
+ // Adds a bogus BR element for empty block elements
12559
+ if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
12560
+ node.innerHTML = '<br data-mce-bogus="1" />';
12099
12561
  }
12100
12562
 
12101
- // Set endpoint if it was normalized
12102
- if (normalized) {
12103
- rng['set' + (start ? 'Start' : 'End')](container, offset);
12104
- }
12563
+ return node;
12105
12564
  }
12106
12565
 
12107
- collapsed = rng.collapsed;
12566
+ if (bookmark) {
12567
+ if (bookmark.start) {
12568
+ rng = dom.createRng();
12569
+ root = dom.getRoot();
12108
12570
 
12109
- normalizeEndPoint(true);
12571
+ if (selection.tridentSel) {
12572
+ return selection.tridentSel.moveToBookmark(bookmark);
12573
+ }
12110
12574
 
12111
- if (!collapsed) {
12112
- normalizeEndPoint();
12113
- }
12575
+ if (setEndPoint(true) && setEndPoint()) {
12576
+ selection.setRng(rng);
12577
+ }
12578
+ } else if (bookmark.id) {
12579
+ // Restore start/end points
12580
+ restoreEndPoint('start');
12581
+ restoreEndPoint('end');
12114
12582
 
12115
- // If it was collapsed then make sure it still is
12116
- if (normalized && collapsed) {
12117
- rng.collapse(true);
12583
+ if (startContainer) {
12584
+ rng = dom.createRng();
12585
+ rng.setStart(addBogus(startContainer), startOffset);
12586
+ rng.setEnd(addBogus(endContainer), endOffset);
12587
+ selection.setRng(rng);
12588
+ }
12589
+ } else if (bookmark.name) {
12590
+ selection.select(dom.select(bookmark.name)[bookmark.index]);
12591
+ } else if (bookmark.rng) {
12592
+ selection.setRng(bookmark.rng);
12593
+ }
12118
12594
  }
12119
-
12120
- return normalized;
12121
12595
  };
12122
12596
  }
12123
12597
 
12124
12598
  /**
12125
- * Compares two ranges and checks if they are equal.
12599
+ * Returns true/false if the specified node is a bookmark node or not.
12126
12600
  *
12127
12601
  * @static
12128
- * @method compareRanges
12129
- * @param {DOMRange} rng1 First range to compare.
12130
- * @param {DOMRange} rng2 First range to compare.
12131
- * @return {Boolean} true/false if the ranges are equal.
12602
+ * @method isBookmarkNode
12603
+ * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
12604
+ * @return {Boolean} true/false if the node is a bookmark node or not.
12132
12605
  */
12133
- RangeUtils.compareRanges = function(rng1, rng2) {
12134
- if (rng1 && rng2) {
12135
- // Compare native IE ranges
12136
- if (rng1.item || rng1.duplicate) {
12137
- // Both are control ranges and the selected element matches
12138
- if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
12139
- return true;
12140
- }
12141
-
12142
- // Both are text ranges and the range matches
12143
- if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
12144
- return true;
12145
- }
12146
- } else {
12147
- // Compare w3c ranges
12148
- return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
12149
- }
12150
- }
12151
-
12152
- return false;
12606
+ BookmarkManager.isBookmarkNode = function(node) {
12607
+ return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
12153
12608
  };
12154
12609
 
12155
- return RangeUtils;
12156
- });
12157
-
12158
- // Included from: js/tinymce/classes/dom/BookmarkManager.js
12159
-
12160
- /**
12161
- * BookmarkManager.js
12162
- *
12163
- * Copyright, Moxiecode Systems AB
12164
- * Released under LGPL License.
12165
- *
12166
- * License: http://www.tinymce.com/license
12167
- * Contributing: http://www.tinymce.com/contributing
12168
- */
12169
-
12170
- /**
12171
- * This class handles selection bookmarks.
12172
- *
12173
- * @class tinymce.dom.BookmarkManager
12174
- */
12175
- define("tinymce/dom/BookmarkManager", [
12176
- "tinymce/Env",
12177
- "tinymce/util/Tools"
12178
- ], function(Env, Tools) {
12179
- /**
12180
- * Constructs a new BookmarkManager instance for a specific selection instance.
12181
- *
12182
- * @constructor
12183
- * @method BookmarkManager
12184
- * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
12185
- */
12186
- function BookmarkManager(selection) {
12187
- var dom = selection.dom;
12188
-
12189
- /**
12190
- * Returns a bookmark location for the current selection. This bookmark object
12191
- * can then be used to restore the selection after some content modification to the document.
12192
- *
12193
- * @method getBookmark
12194
- * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
12195
- * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
12196
- * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
12197
- * @example
12198
- * // Stores a bookmark of the current selection
12199
- * var bm = tinymce.activeEditor.selection.getBookmark();
12200
- *
12201
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12202
- *
12203
- * // Restore the selection bookmark
12204
- * tinymce.activeEditor.selection.moveToBookmark(bm);
12205
- */
12206
- this.getBookmark = function(type, normalized) {
12207
- var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
12208
-
12209
- function findIndex(name, element) {
12210
- var index = 0;
12211
-
12212
- Tools.each(dom.select(name), function(node, i) {
12213
- if (node == element) {
12214
- index = i;
12215
- }
12216
- });
12217
-
12218
- return index;
12219
- }
12220
-
12221
- function normalizeTableCellSelection(rng) {
12222
- function moveEndPoint(start) {
12223
- var container, offset, childNodes, prefix = start ? 'start' : 'end';
12224
-
12225
- container = rng[prefix + 'Container'];
12226
- offset = rng[prefix + 'Offset'];
12227
-
12228
- if (container.nodeType == 1 && container.nodeName == "TR") {
12229
- childNodes = container.childNodes;
12230
- container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
12231
- if (container) {
12232
- offset = start ? 0 : container.childNodes.length;
12233
- rng['set' + (start ? 'Start' : 'End')](container, offset);
12234
- }
12235
- }
12236
- }
12237
-
12238
- moveEndPoint(true);
12239
- moveEndPoint();
12240
-
12241
- return rng;
12242
- }
12243
-
12244
- function getLocation() {
12245
- var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
12246
-
12247
- function getPoint(rng, start) {
12248
- var container = rng[start ? 'startContainer' : 'endContainer'],
12249
- offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
12250
-
12251
- if (container.nodeType == 3) {
12252
- if (normalized) {
12253
- for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
12254
- offset += node.nodeValue.length;
12255
- }
12256
- }
12257
-
12258
- point.push(offset);
12259
- } else {
12260
- childNodes = container.childNodes;
12261
-
12262
- if (offset >= childNodes.length && childNodes.length) {
12263
- after = 1;
12264
- offset = Math.max(0, childNodes.length - 1);
12265
- }
12266
-
12267
- point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
12268
- }
12269
-
12270
- for (; container && container != root; container = container.parentNode) {
12271
- point.push(dom.nodeIndex(container, normalized));
12272
- }
12273
-
12274
- return point;
12275
- }
12276
-
12277
- bookmark.start = getPoint(rng, true);
12278
-
12279
- if (!selection.isCollapsed()) {
12280
- bookmark.end = getPoint(rng);
12281
- }
12282
-
12283
- return bookmark;
12284
- }
12285
-
12286
- if (type == 2) {
12287
- element = selection.getNode();
12288
- name = element ? element.nodeName : null;
12289
-
12290
- if (name == 'IMG') {
12291
- return {name: name, index: findIndex(name, element)};
12292
- }
12293
-
12294
- if (selection.tridentSel) {
12295
- return selection.tridentSel.getBookmark(type);
12296
- }
12297
-
12298
- return getLocation();
12299
- }
12300
-
12301
- // Handle simple range
12302
- if (type) {
12303
- return {rng: selection.getRng()};
12304
- }
12305
-
12306
- rng = selection.getRng();
12307
- id = dom.uniqueId();
12308
- collapsed = selection.isCollapsed();
12309
- styles = 'overflow:hidden;line-height:0px';
12310
-
12311
- // Explorer method
12312
- if (rng.duplicate || rng.item) {
12313
- // Text selection
12314
- if (!rng.item) {
12315
- rng2 = rng.duplicate();
12316
-
12317
- try {
12318
- // Insert start marker
12319
- rng.collapse();
12320
- rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
12321
-
12322
- // Insert end marker
12323
- if (!collapsed) {
12324
- rng2.collapse(false);
12325
-
12326
- // Detect the empty space after block elements in IE and move the
12327
- // end back one character <p></p>] becomes <p>]</p>
12328
- rng.moveToElementText(rng2.parentElement());
12329
- if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
12330
- rng2.move('character', -1);
12331
- }
12332
-
12333
- rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
12334
- }
12335
- } catch (ex) {
12336
- // IE might throw unspecified error so lets ignore it
12337
- return null;
12338
- }
12339
- } else {
12340
- // Control selection
12341
- element = rng.item(0);
12342
- name = element.nodeName;
12343
-
12344
- return {name: name, index: findIndex(name, element)};
12345
- }
12346
- } else {
12347
- element = selection.getNode();
12348
- name = element.nodeName;
12349
- if (name == 'IMG') {
12350
- return {name: name, index: findIndex(name, element)};
12351
- }
12352
-
12353
- // W3C method
12354
- rng2 = normalizeTableCellSelection(rng.cloneRange());
12355
-
12356
- // Insert end marker
12357
- if (!collapsed) {
12358
- rng2.collapse(false);
12359
- rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
12360
- }
12361
-
12362
- rng = normalizeTableCellSelection(rng);
12363
- rng.collapse(true);
12364
- rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
12365
- }
12366
-
12367
- selection.moveToBookmark({id: id, keep: 1});
12368
-
12369
- return {id: id};
12370
- };
12371
-
12372
- /**
12373
- * Restores the selection to the specified bookmark.
12374
- *
12375
- * @method moveToBookmark
12376
- * @param {Object} bookmark Bookmark to restore selection from.
12377
- * @return {Boolean} true/false if it was successful or not.
12378
- * @example
12379
- * // Stores a bookmark of the current selection
12380
- * var bm = tinymce.activeEditor.selection.getBookmark();
12381
- *
12382
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12383
- *
12384
- * // Restore the selection bookmark
12385
- * tinymce.activeEditor.selection.moveToBookmark(bm);
12386
- */
12387
- this.moveToBookmark = function(bookmark) {
12388
- var rng, root, startContainer, endContainer, startOffset, endOffset;
12389
-
12390
- function setEndPoint(start) {
12391
- var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
12392
-
12393
- if (point) {
12394
- offset = point[0];
12395
-
12396
- // Find container node
12397
- for (node = root, i = point.length - 1; i >= 1; i--) {
12398
- children = node.childNodes;
12399
-
12400
- if (point[i] > children.length - 1) {
12401
- return;
12402
- }
12403
-
12404
- node = children[point[i]];
12405
- }
12406
-
12407
- // Move text offset to best suitable location
12408
- if (node.nodeType === 3) {
12409
- offset = Math.min(point[0], node.nodeValue.length);
12410
- }
12411
-
12412
- // Move element offset to best suitable location
12413
- if (node.nodeType === 1) {
12414
- offset = Math.min(point[0], node.childNodes.length);
12415
- }
12416
-
12417
- // Set offset within container node
12418
- if (start) {
12419
- rng.setStart(node, offset);
12420
- } else {
12421
- rng.setEnd(node, offset);
12422
- }
12423
- }
12424
-
12425
- return true;
12426
- }
12427
-
12428
- function restoreEndPoint(suffix) {
12429
- var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
12430
-
12431
- if (marker) {
12432
- node = marker.parentNode;
12433
-
12434
- if (suffix == 'start') {
12435
- if (!keep) {
12436
- idx = dom.nodeIndex(marker);
12437
- } else {
12438
- node = marker.firstChild;
12439
- idx = 1;
12440
- }
12441
-
12442
- startContainer = endContainer = node;
12443
- startOffset = endOffset = idx;
12444
- } else {
12445
- if (!keep) {
12446
- idx = dom.nodeIndex(marker);
12447
- } else {
12448
- node = marker.firstChild;
12449
- idx = 1;
12450
- }
12451
-
12452
- endContainer = node;
12453
- endOffset = idx;
12454
- }
12455
-
12456
- if (!keep) {
12457
- prev = marker.previousSibling;
12458
- next = marker.nextSibling;
12459
-
12460
- // Remove all marker text nodes
12461
- Tools.each(Tools.grep(marker.childNodes), function(node) {
12462
- if (node.nodeType == 3) {
12463
- node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
12464
- }
12465
- });
12466
-
12467
- // Remove marker but keep children if for example contents where inserted into the marker
12468
- // Also remove duplicated instances of the marker for example by a
12469
- // split operation or by WebKit auto split on paste feature
12470
- while ((marker = dom.get(bookmark.id + '_' + suffix))) {
12471
- dom.remove(marker, 1);
12472
- }
12473
-
12474
- // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
12475
- // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
12476
- // isn't worth the effort. Sorry, Opera but it's just a fact
12477
- if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
12478
- idx = prev.nodeValue.length;
12479
- prev.appendData(next.nodeValue);
12480
- dom.remove(next);
12481
-
12482
- if (suffix == 'start') {
12483
- startContainer = endContainer = prev;
12484
- startOffset = endOffset = idx;
12485
- } else {
12486
- endContainer = prev;
12487
- endOffset = idx;
12488
- }
12489
- }
12490
- }
12491
- }
12492
- }
12493
-
12494
- function addBogus(node) {
12495
- // Adds a bogus BR element for empty block elements
12496
- if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
12497
- node.innerHTML = '<br data-mce-bogus="1" />';
12498
- }
12499
-
12500
- return node;
12501
- }
12502
-
12503
- if (bookmark) {
12504
- if (bookmark.start) {
12505
- rng = dom.createRng();
12506
- root = dom.getRoot();
12507
-
12508
- if (selection.tridentSel) {
12509
- return selection.tridentSel.moveToBookmark(bookmark);
12510
- }
12511
-
12512
- if (setEndPoint(true) && setEndPoint()) {
12513
- selection.setRng(rng);
12514
- }
12515
- } else if (bookmark.id) {
12516
- // Restore start/end points
12517
- restoreEndPoint('start');
12518
- restoreEndPoint('end');
12519
-
12520
- if (startContainer) {
12521
- rng = dom.createRng();
12522
- rng.setStart(addBogus(startContainer), startOffset);
12523
- rng.setEnd(addBogus(endContainer), endOffset);
12524
- selection.setRng(rng);
12525
- }
12526
- } else if (bookmark.name) {
12527
- selection.select(dom.select(bookmark.name)[bookmark.index]);
12528
- } else if (bookmark.rng) {
12529
- selection.setRng(bookmark.rng);
12530
- }
12531
- }
12532
- };
12533
- }
12534
-
12535
- /**
12536
- * Returns true/false if the specified node is a bookmark node or not.
12537
- *
12538
- * @static
12539
- * @method isBookmarkNode
12540
- * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
12541
- * @return {Boolean} true/false if the node is a bookmark node or not.
12542
- */
12543
- BookmarkManager.isBookmarkNode = function(node) {
12544
- return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
12545
- };
12546
-
12547
- return BookmarkManager;
12610
+ return BookmarkManager;
12548
12611
  });
12549
12612
 
12550
12613
  // Included from: js/tinymce/classes/dom/Selection.js
@@ -12981,9 +13044,9 @@ define("tinymce/dom/Selection", [
12981
13044
  * Collapse the selection to start or end of range.
12982
13045
  *
12983
13046
  * @method collapse
12984
- * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
13047
+ * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
12985
13048
  */
12986
- collapse: function(to_start) {
13049
+ collapse: function(toStart) {
12987
13050
  var self = this, rng = self.getRng(), node;
12988
13051
 
12989
13052
  // Control range on IE
@@ -12993,7 +13056,7 @@ define("tinymce/dom/Selection", [
12993
13056
  rng.moveToElementText(node);
12994
13057
  }
12995
13058
 
12996
- rng.collapse(!!to_start);
13059
+ rng.collapse(!!toStart);
12997
13060
  self.setRng(rng);
12998
13061
  },
12999
13062
 
@@ -14138,62 +14201,6 @@ define("tinymce/Formatter", [
14138
14201
  return rng;
14139
14202
  }
14140
14203
 
14141
- function applyStyleToList(node, bookmark, wrapElm, newWrappers, process) {
14142
- var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14143
-
14144
- // find the index of the first child list.
14145
- each(node.childNodes, function(n, index) {
14146
- if (n.nodeName === "UL" || n.nodeName === "OL") {
14147
- listIndex = index;
14148
- list = n;
14149
- return false;
14150
- }
14151
- });
14152
-
14153
- // get the index of the bookmarks
14154
- each(node.childNodes, function(n, index) {
14155
- if (isBookmarkNode(n)) {
14156
- if (n.id == bookmark.id + "_start") {
14157
- startIndex = index;
14158
- } else if (n.id == bookmark.id + "_end") {
14159
- endIndex = index;
14160
- }
14161
- }
14162
- });
14163
-
14164
- // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14165
- if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
14166
- each(grep(node.childNodes), process);
14167
- return 0;
14168
- } else {
14169
- currentWrapElm = dom.clone(wrapElm, FALSE);
14170
-
14171
- // create a list of the nodes on the same side of the list as the selection
14172
- each(grep(node.childNodes), function(n, index) {
14173
- if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
14174
- nodes.push(n);
14175
- n.parentNode.removeChild(n);
14176
- }
14177
- });
14178
-
14179
- // insert the wrapping element either before or after the list.
14180
- if (startIndex < listIndex) {
14181
- node.insertBefore(currentWrapElm, list);
14182
- } else if (startIndex > listIndex) {
14183
- node.insertBefore(currentWrapElm, list.nextSibling);
14184
- }
14185
-
14186
- // add the new nodes to the list.
14187
- newWrappers.push(currentWrapElm);
14188
-
14189
- each(nodes, function(node) {
14190
- currentWrapElm.appendChild(node);
14191
- });
14192
-
14193
- return currentWrapElm;
14194
- }
14195
- }
14196
-
14197
14204
  function applyRngStyle(rng, bookmark, node_specific) {
14198
14205
  var newWrappers = [], wrapName, wrapElm, contentEditable = true;
14199
14206
 
@@ -14290,10 +14297,6 @@ define("tinymce/Formatter", [
14290
14297
  }
14291
14298
 
14292
14299
  currentWrapElm.appendChild(node);
14293
- } else if (nodeName == 'li' && bookmark) {
14294
- // Start wrapping - if we are in a list node and have a bookmark, then
14295
- // we will always begin by wrapping in a new element.
14296
- currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
14297
14300
  } else {
14298
14301
  // Start a new wrapper for possible children
14299
14302
  currentWrapElm = 0;
@@ -14558,12 +14561,12 @@ define("tinymce/Formatter", [
14558
14561
  return formatRoot;
14559
14562
  }
14560
14563
 
14561
- function wrapAndSplit(format_root, container, target, split) {
14564
+ function wrapAndSplit(formatRoot, container, target, split) {
14562
14565
  var parent, clone, lastClone, firstClone, i, formatRootParent;
14563
14566
 
14564
14567
  // Format root found then clone formats and split it
14565
- if (format_root) {
14566
- formatRootParent = format_root.parentNode;
14568
+ if (formatRoot) {
14569
+ formatRootParent = formatRoot.parentNode;
14567
14570
 
14568
14571
  for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14569
14572
  clone = dom.clone(parent, FALSE);
@@ -14590,8 +14593,8 @@ define("tinymce/Formatter", [
14590
14593
  }
14591
14594
 
14592
14595
  // Never split block elements if the format is mixed
14593
- if (split && (!format.mixed || !isBlock(format_root))) {
14594
- container = dom.split(format_root, container);
14596
+ if (split && (!format.mixed || !isBlock(formatRoot))) {
14597
+ container = dom.split(formatRoot, container);
14595
14598
  }
14596
14599
 
14597
14600
  // Wrap container in cloned formats
@@ -14955,6 +14958,11 @@ define("tinymce/Formatter", [
14955
14958
  ed.on('NodeChange', function(e) {
14956
14959
  var parents = getParents(e.element), matchedFormats = {};
14957
14960
 
14961
+ // Ignore bogus nodes like the <a> tag created by moveStart()
14962
+ parents = Tools.grep(parents, function(node) {
14963
+ return !node.getAttribute('data-mce-bogus');
14964
+ });
14965
+
14958
14966
  // Check for new formats
14959
14967
  each(formatChangeData, function(callbacks, format) {
14960
14968
  each(parents, function(node) {
@@ -16139,7 +16147,7 @@ define("tinymce/Formatter", [
16139
16147
  if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16140
16148
  // IE has a "neat" feature where it moves the start node into the closest element
16141
16149
  // we can avoid this by inserting an element before it and then remove it after we set the selection
16142
- tmpNode = dom.create('a', null, INVISIBLE_CHAR);
16150
+ tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
16143
16151
  node.parentNode.insertBefore(tmpNode, node);
16144
16152
 
16145
16153
  // Set selection and remove tmpNode
@@ -16325,38 +16333,6 @@ define("tinymce/UndoManager", [
16325
16333
  }
16326
16334
  });
16327
16335
 
16328
- // Selection range isn't updated until after the click events default handler is executed
16329
- // so we need to wait for the selection to update on Gecko/WebKit it happens right away.
16330
- // On IE it might take a while so we listen for the SelectionChange event.
16331
- //
16332
- // We can't use the SelectionChange on all browsers event since Gecko doesn't support that.
16333
- if (Env.ie) {
16334
- editor.on('MouseUp', function(e) {
16335
- if (!e.isDefaultPrevented()) {
16336
- editor.once('SelectionChange', function() {
16337
- // Selection change might fire when focus is lost
16338
- if (editor.dom.isChildOf(editor.selection.getStart(), editor.getBody())) {
16339
- editor.nodeChanged();
16340
- }
16341
- });
16342
-
16343
- editor.nodeChanged();
16344
- }
16345
- });
16346
- } else {
16347
- editor.on('MouseUp', function() {
16348
- editor.nodeChanged();
16349
- });
16350
-
16351
- editor.on('Click', function(e) {
16352
- if (!e.isDefaultPrevented()) {
16353
- setTimeout(function() {
16354
- editor.nodeChanged();
16355
- }, 0);
16356
- }
16357
- });
16358
- }
16359
-
16360
16336
  self = {
16361
16337
  // Explose for debugging reasons
16362
16338
  data: data,
@@ -21577,7 +21553,13 @@ define("tinymce/ui/KeyboardNavigation", [
21577
21553
  return function(settings) {
21578
21554
  var root = settings.root, focusedElement, focusedControl;
21579
21555
 
21580
- focusedElement = document.activeElement;
21556
+ try {
21557
+ focusedElement = document.activeElement;
21558
+ } catch (ex) {
21559
+ // IE sometimes fails to return a proper element
21560
+ focusedElement = document.body;
21561
+ }
21562
+
21581
21563
  focusedControl = root.getParentCtrl(focusedElement);
21582
21564
 
21583
21565
  /**
@@ -23953,44 +23935,41 @@ define("tinymce/ui/MessageBox", [
23953
23935
  msgBox: function(settings) {
23954
23936
  var buttons, callback = settings.callback || function() {};
23955
23937
 
23938
+ function createButton(text, status, primary) {
23939
+ return {
23940
+ type: "button",
23941
+ text: text,
23942
+ subtype: primary ? 'primary' : '',
23943
+ onClick: function(e) {
23944
+ e.control.parents()[1].close();
23945
+ callback(status);
23946
+ }
23947
+ };
23948
+ }
23949
+
23956
23950
  switch (settings.buttons) {
23957
23951
  case MessageBox.OK_CANCEL:
23958
23952
  buttons = [
23959
- {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
23960
- e.control.parents()[1].close();
23961
- callback(true);
23962
- }},
23963
-
23964
- {type: "button", text: "Cancel", onClick: function(e) {
23965
- e.control.parents()[1].close();
23966
- callback(false);
23967
- }}
23953
+ createButton('Ok', true, true),
23954
+ createButton('Cancel', false)
23968
23955
  ];
23969
23956
  break;
23970
23957
 
23971
23958
  case MessageBox.YES_NO:
23972
- buttons = [
23973
- {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
23974
- e.control.parents()[1].close();
23975
- callback(true);
23976
- }}
23977
- ];
23978
- break;
23979
-
23980
23959
  case MessageBox.YES_NO_CANCEL:
23981
23960
  buttons = [
23982
- {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
23983
- e.control.parents()[1].close();
23984
- }}
23961
+ createButton('Yes', 1, true),
23962
+ createButton('No', 0)
23985
23963
  ];
23964
+
23965
+ if (settings.buttons == MessageBox.YES_NO_CANCEL) {
23966
+ buttons.push(createButton('Cancel', -1));
23967
+ }
23986
23968
  break;
23987
23969
 
23988
23970
  default:
23989
23971
  buttons = [
23990
- {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
23991
- e.control.parents()[1].close();
23992
- callback(true);
23993
- }}
23972
+ createButton('Ok', true, true)
23994
23973
  ];
23995
23974
  break;
23996
23975
  }
@@ -24802,8 +24781,6 @@ define("tinymce/util/Quirks", [
24802
24781
  if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
24803
24782
  selection.select(e);
24804
24783
  }
24805
-
24806
- editor.nodeChanged();
24807
24784
  });
24808
24785
  }
24809
24786
 
@@ -25433,6 +25410,26 @@ define("tinymce/util/Quirks", [
25433
25410
  });
25434
25411
  }
25435
25412
 
25413
+ /**
25414
+ * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
25415
+ *
25416
+ * Scenario:
25417
+ * 1) Create a table 2x2.
25418
+ * 2) Select and copy cells A2-B2.
25419
+ * 3) Paste and it will add BR element to table cell.
25420
+ */
25421
+ function removeAppleInterchangeBrs() {
25422
+ parser.addNodeFilter('br', function(nodes) {
25423
+ var i = nodes.length;
25424
+
25425
+ while (i--) {
25426
+ if (nodes[i].attr('class') == 'Apple-interchange-newline') {
25427
+ nodes[i].remove();
25428
+ }
25429
+ }
25430
+ });
25431
+ }
25432
+
25436
25433
  // All browsers
25437
25434
  removeBlockQuoteOnBackSpace();
25438
25435
  emptyEditorWhenDeleting();
@@ -25446,6 +25443,7 @@ define("tinymce/util/Quirks", [
25446
25443
  setDefaultBlockType();
25447
25444
  blockFormSubmitInsideEditor();
25448
25445
  disableBackspaceIntoATable();
25446
+ removeAppleInterchangeBrs();
25449
25447
 
25450
25448
  // iOS
25451
25449
  if (Env.iOS) {
@@ -25925,6 +25923,7 @@ define("tinymce/Editor", [
25925
25923
  "tinymce/dom/DOMUtils",
25926
25924
  "tinymce/dom/DomQuery",
25927
25925
  "tinymce/AddOnManager",
25926
+ "tinymce/NodeChange",
25928
25927
  "tinymce/html/Node",
25929
25928
  "tinymce/dom/Serializer",
25930
25929
  "tinymce/html/Serializer",
@@ -25946,7 +25945,7 @@ define("tinymce/Editor", [
25946
25945
  "tinymce/EditorObservable",
25947
25946
  "tinymce/Shortcuts"
25948
25947
  ], function(
25949
- DOMUtils, DomQuery, AddOnManager, Node, DomSerializer, Serializer,
25948
+ DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
25950
25949
  Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
25951
25950
  URI, ScriptLoader, EventUtils, WindowManager,
25952
25951
  Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
@@ -26018,9 +26017,9 @@ define("tinymce/Editor", [
26018
26017
  inline_styles: true,
26019
26018
  convert_fonts_to_spans: true,
26020
26019
  indent: 'simple',
26021
- indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
26020
+ indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26022
26021
  'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26023
- indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
26022
+ indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26024
26023
  'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26025
26024
  validate: true,
26026
26025
  entity_encoding: 'named',
@@ -26125,6 +26124,10 @@ define("tinymce/Editor", [
26125
26124
  self.queryValueCommands = {};
26126
26125
  self.loadedCSS = {};
26127
26126
 
26127
+ if (settings.target) {
26128
+ self.targetElm = settings.target;
26129
+ }
26130
+
26128
26131
  self.suffix = editorManager.suffix;
26129
26132
  self.editorManager = editorManager;
26130
26133
  self.inline = settings.inline;
@@ -26434,12 +26437,6 @@ define("tinymce/Editor", [
26434
26437
 
26435
26438
  // Resize editor
26436
26439
  if (!settings.content_editable) {
26437
- DOM.setStyles(o.sizeContainer || o.editorContainer, {
26438
- wi2dth: w,
26439
- // TODO: Fix this
26440
- h2eight: h
26441
- });
26442
-
26443
26440
  h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
26444
26441
  if (h < minHeight) {
26445
26442
  h = minHeight;
@@ -26517,8 +26514,9 @@ define("tinymce/Editor", [
26517
26514
  bodyClass = bodyClass[self.id] || '';
26518
26515
  }
26519
26516
 
26520
- self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
26521
- 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
26517
+ self.iframeHTML += '</head><body id="' + bodyId +
26518
+ '" class="mce-content-body ' + bodyClass +
26519
+ '" data-id="' + self.id + '"><br></body></html>';
26522
26520
 
26523
26521
  /*eslint no-script-url:0 */
26524
26522
  var domainRelaxUrl = 'javascript:(function(){' +
@@ -26533,14 +26531,14 @@ define("tinymce/Editor", [
26533
26531
 
26534
26532
  // Create iframe
26535
26533
  // TODO: ACC add the appropriate description on this.
26536
- n = DOM.add(o.iframeContainer, 'iframe', {
26534
+ var ifr = DOM.create('iframe', {
26537
26535
  id: self.id + "_ifr",
26538
- src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
26536
+ //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
26539
26537
  frameBorder: '0',
26540
26538
  allowTransparency: "true",
26541
26539
  title: self.editorManager.translate(
26542
- "Rich Text Area. Press ALT-F9 for menu. " +
26543
- "Press ALT-F10 for toolbar. Press ALT-0 for help"
26540
+ "Rich Text Area. Press ALT-F9 for menu. " +
26541
+ "Press ALT-F10 for toolbar. Press ALT-0 for help"
26544
26542
  ),
26545
26543
  style: {
26546
26544
  width: '100%',
@@ -26549,6 +26547,15 @@ define("tinymce/Editor", [
26549
26547
  }
26550
26548
  });
26551
26549
 
26550
+ ifr.onload = function() {
26551
+ ifr.onload = null;
26552
+ self.fire("load");
26553
+ };
26554
+
26555
+ DOM.setAttrib("src", url || 'javascript:""');
26556
+
26557
+ n = DOM.add(o.iframeContainer, ifr);
26558
+
26552
26559
  // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
26553
26560
  // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
26554
26561
  if (ie) {
@@ -26560,12 +26567,14 @@ define("tinymce/Editor", [
26560
26567
  }
26561
26568
 
26562
26569
  self.contentAreaContainer = o.iframeContainer;
26570
+ self.iframeElement = ifr;
26563
26571
 
26564
26572
  if (o.editorContainer) {
26565
26573
  DOM.get(o.editorContainer).style.display = self.orgDisplay;
26574
+ self.hidden = DOM.isHidden(o.editorContainer);
26566
26575
  }
26567
26576
 
26568
- DOM.get(self.id).style.display = 'none';
26577
+ self.getElement().style.display = 'none';
26569
26578
  DOM.setAttrib(self.id, 'aria-hidden', true);
26570
26579
 
26571
26580
  if (!url) {
@@ -26583,7 +26592,7 @@ define("tinymce/Editor", [
26583
26592
  * @private
26584
26593
  */
26585
26594
  initContentBody: function(skipWrite) {
26586
- var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
26595
+ var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
26587
26596
 
26588
26597
  // Restore visibility on target element
26589
26598
  if (!settings.inline) {
@@ -26656,7 +26665,7 @@ define("tinymce/Editor", [
26656
26665
  hex_colors: settings.force_hex_style_colors,
26657
26666
  class_filter: settings.class_filter,
26658
26667
  update_styles: true,
26659
- root_element: settings.content_editable ? self.id : null,
26668
+ root_element: self.inline ? self.getBody() : null,
26660
26669
  collect: settings.content_editable,
26661
26670
  schema: self.schema,
26662
26671
  onSetAttrib: function(e) {
@@ -26730,7 +26739,7 @@ define("tinymce/Editor", [
26730
26739
  node = nodes[i];
26731
26740
 
26732
26741
  if (node.isEmpty(nonEmptyElements)) {
26733
- node.empty().append(new Node('br', 1)).shortEnded = true;
26742
+ node.append(new Node('br', 1)).shortEnded = true;
26734
26743
  }
26735
26744
  }
26736
26745
  });
@@ -26785,6 +26794,7 @@ define("tinymce/Editor", [
26785
26794
  self.forceBlocks = new ForceBlocks(self);
26786
26795
  self.enterKey = new EnterKey(self);
26787
26796
  self.editorCommands = new EditorCommands(self);
26797
+ self._nodeChangeDispatcher = new NodeChange(self);
26788
26798
 
26789
26799
  self.fire('PreInit');
26790
26800
 
@@ -26795,7 +26805,7 @@ define("tinymce/Editor", [
26795
26805
 
26796
26806
  self.fire('PostRender');
26797
26807
 
26798
- self.quirks = Quirks(self);
26808
+ self.quirks = new Quirks(self);
26799
26809
 
26800
26810
  if (settings.directionality) {
26801
26811
  body.dir = settings.directionality;
@@ -26887,13 +26897,13 @@ define("tinymce/Editor", [
26887
26897
  * it will also place DOM focus inside the editor.
26888
26898
  *
26889
26899
  * @method focus
26890
- * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
26900
+ * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
26891
26901
  */
26892
- focus: function(skip_focus) {
26902
+ focus: function(skipFocus) {
26893
26903
  var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
26894
26904
  var controlElm, doc = self.getDoc(), body;
26895
26905
 
26896
- if (!skip_focus) {
26906
+ if (!skipFocus) {
26897
26907
  // Get selected control element
26898
26908
  rng = selection.getRng();
26899
26909
  if (rng.item) {
@@ -27070,36 +27080,7 @@ define("tinymce/Editor", [
27070
27080
  * @param {Object} args Optional args to pass to NodeChange event handlers.
27071
27081
  */
27072
27082
  nodeChanged: function(args) {
27073
- var self = this, selection = self.selection, node, parents, root;
27074
-
27075
- // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
27076
- if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
27077
- // Get start node
27078
- root = self.getBody();
27079
- node = selection.getStart() || root;
27080
- node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
27081
-
27082
- // Edge case for <p>|<img></p>
27083
- if (node.nodeName == 'IMG' && selection.isCollapsed()) {
27084
- node = node.parentNode;
27085
- }
27086
-
27087
- // Get parents and add them to object
27088
- parents = [];
27089
- self.dom.getParent(node, function(node) {
27090
- if (node === root) {
27091
- return true;
27092
- }
27093
-
27094
- parents.push(node);
27095
- });
27096
-
27097
- args = args || {};
27098
- args.element = node;
27099
- args.parents = parents;
27100
-
27101
- self.fire('NodeChange', args);
27102
- }
27083
+ this._nodeChangeDispatcher.nodeChanged(args);
27103
27084
  },
27104
27085
 
27105
27086
  /**
@@ -27645,7 +27626,7 @@ define("tinymce/Editor", [
27645
27626
  content = '<br data-mce-bogus="1">';
27646
27627
  }
27647
27628
 
27648
- body.innerHTML = content;
27629
+ self.dom.setHTML(body, content);
27649
27630
 
27650
27631
  self.fire('SetContent', args);
27651
27632
  } else {
@@ -27793,7 +27774,11 @@ define("tinymce/Editor", [
27793
27774
  * @return {Element} HTML DOM element for the replaced element.
27794
27775
  */
27795
27776
  getElement: function() {
27796
- return DOM.get(this.settings.content_element || this.id);
27777
+ if (!this.targetElm) {
27778
+ this.targetElm = DOM.get(this.id);
27779
+ }
27780
+
27781
+ return this.targetElm;
27797
27782
  },
27798
27783
 
27799
27784
  /**
@@ -27806,7 +27791,7 @@ define("tinymce/Editor", [
27806
27791
  var self = this, elm;
27807
27792
 
27808
27793
  if (!self.contentWindow) {
27809
- elm = DOM.get(self.id + "_ifr");
27794
+ elm = self.iframeElement;
27810
27795
 
27811
27796
  if (elm) {
27812
27797
  self.contentWindow = elm.contentWindow;
@@ -27904,12 +27889,10 @@ define("tinymce/Editor", [
27904
27889
  cls = settings.visual_table_class || 'mce-item-table';
27905
27890
  value = dom.getAttrib(elm, 'border');
27906
27891
 
27907
- if (!value || value == '0') {
27908
- if (self.hasVisual) {
27909
- dom.addClass(elm, cls);
27910
- } else {
27911
- dom.removeClass(elm, cls);
27912
- }
27892
+ if ((!value || value == '0') && self.hasVisual) {
27893
+ dom.addClass(elm, cls);
27894
+ } else {
27895
+ dom.removeClass(elm, cls);
27913
27896
  }
27914
27897
 
27915
27898
  return;
@@ -27919,12 +27902,10 @@ define("tinymce/Editor", [
27919
27902
  value = dom.getAttrib(elm, 'name') || elm.id;
27920
27903
  cls = settings.visual_anchor_class || 'mce-item-anchor';
27921
27904
 
27922
- if (value) {
27923
- if (self.hasVisual) {
27924
- dom.addClass(elm, cls);
27925
- } else {
27926
- dom.removeClass(elm, cls);
27927
- }
27905
+ if (value && self.hasVisual) {
27906
+ dom.addClass(elm, cls);
27907
+ } else {
27908
+ dom.removeClass(elm, cls);
27928
27909
  }
27929
27910
  }
27930
27911
 
@@ -28035,7 +28016,8 @@ define("tinymce/Editor", [
28035
28016
  }
28036
28017
 
28037
28018
  self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
28038
- self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
28019
+ self.bodyElement = self.contentDocument = self.contentWindow = null;
28020
+ self.iframeElement = self.targetElm = null;
28039
28021
 
28040
28022
  if (self.selection) {
28041
28023
  self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
@@ -28250,19 +28232,35 @@ define("tinymce/FocusManager", [
28250
28232
  editor.on('init', function() {
28251
28233
  // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
28252
28234
  if (editor.inline || Env.ie) {
28253
- // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
28254
- editor.on('nodechange keyup', function() {
28255
- var node = document.activeElement;
28235
+ // Use the onbeforedeactivate event when available since it works better see #7023
28236
+ if ("onbeforedeactivate" in document && Env.ie < 9) {
28237
+ editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
28238
+ try {
28239
+ editor.lastRng = editor.selection.getRng();
28240
+ } catch (ex) {
28241
+ // IE throws "Unexcpected call to method or property access" some times so lets ignore it
28242
+ }
28243
+ });
28244
+ } else {
28245
+ // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
28246
+ editor.on('nodechange mouseup keyup', function(e) {
28247
+ var node = getActiveElement();
28256
28248
 
28257
- // IE 11 reports active element as iframe not body of iframe
28258
- if (node && node.id == editor.id + '_ifr') {
28259
- node = editor.getBody();
28260
- }
28249
+ // Only act on manual nodechanges
28250
+ if (e.type == 'nodechange' && e.selectionChange) {
28251
+ return;
28252
+ }
28261
28253
 
28262
- if (editor.dom.isChildOf(node, editor.getBody())) {
28263
- editor.lastRng = editor.selection.getRng();
28264
- }
28265
- });
28254
+ // IE 11 reports active element as iframe not body of iframe
28255
+ if (node && node.id == editor.id + '_ifr') {
28256
+ node = editor.getBody();
28257
+ }
28258
+
28259
+ if (editor.dom.isChildOf(node, editor.getBody())) {
28260
+ editor.lastRng = editor.selection.getRng();
28261
+ }
28262
+ });
28263
+ }
28266
28264
 
28267
28265
  // Handles the issue with WebKit not retaining selection within inline document
28268
28266
  // If the user releases the mouse out side the body since a mouse up event wont occur on the body
@@ -28340,8 +28338,9 @@ define("tinymce/FocusManager", [
28340
28338
  var activeEditor = editorManager.activeEditor;
28341
28339
 
28342
28340
  if (activeEditor && e.target.ownerDocument == document) {
28343
- // Check to make sure we have a valid selection
28344
- if (activeEditor.selection) {
28341
+ // Check to make sure we have a valid selection don't update the bookmark if it's
28342
+ // a focusin to the body of the editor see #7025
28343
+ if (activeEditor.selection && e.target != activeEditor.getBody()) {
28345
28344
  activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
28346
28345
  }
28347
28346
 
@@ -28505,7 +28504,7 @@ define("tinymce/EditorManager", [
28505
28504
  * @property minorVersion
28506
28505
  * @type String
28507
28506
  */
28508
- minorVersion: '1.0',
28507
+ minorVersion: '1.2',
28509
28508
 
28510
28509
  /**
28511
28510
  * Release date of TinyMCE build.
@@ -28513,7 +28512,7 @@ define("tinymce/EditorManager", [
28513
28512
  * @property releaseDate
28514
28513
  * @type String
28515
28514
  */
28516
- releaseDate: '2014-06-18',
28515
+ releaseDate: '2014-07-15',
28517
28516
 
28518
28517
  /**
28519
28518
  * Collection of editor instances.
@@ -28673,26 +28672,27 @@ define("tinymce/EditorManager", [
28673
28672
  return id;
28674
28673
  }
28675
28674
 
28676
- function createEditor(id, settings) {
28675
+ function createEditor(id, settings, targetElm) {
28677
28676
  if (!purgeDestroyedEditor(self.get(id))) {
28678
28677
  var editor = new Editor(id, settings, self);
28678
+ editor.targetElm = editor.targetElm || targetElm;
28679
28679
  editors.push(editor);
28680
28680
  editor.render();
28681
28681
  }
28682
28682
  }
28683
28683
 
28684
- function execCallback(se, n, s) {
28685
- var f = se[n];
28684
+ function execCallback(name) {
28685
+ var callback = settings[name];
28686
28686
 
28687
- if (!f) {
28687
+ if (!callback) {
28688
28688
  return;
28689
28689
  }
28690
28690
 
28691
- return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
28691
+ return callback.apply(self, Array.prototype.slice.call(arguments, 2));
28692
28692
  }
28693
28693
 
28694
- function hasClass(n, c) {
28695
- return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
28694
+ function hasClass(elm, className) {
28695
+ return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
28696
28696
  }
28697
28697
 
28698
28698
  function readyHandler() {
@@ -28700,13 +28700,13 @@ define("tinymce/EditorManager", [
28700
28700
 
28701
28701
  DOM.unbind(window, 'ready', readyHandler);
28702
28702
 
28703
- execCallback(settings, 'onpageload');
28703
+ execCallback('onpageload');
28704
28704
 
28705
28705
  if (settings.types) {
28706
28706
  // Process type specific selector
28707
28707
  each(settings.types, function(type) {
28708
28708
  each(DOM.select(type.selector), function(elm) {
28709
- createEditor(createId(elm), extend({}, settings, type));
28709
+ createEditor(createId(elm), extend({}, settings, type), elm);
28710
28710
  });
28711
28711
  });
28712
28712
 
@@ -28714,10 +28714,12 @@ define("tinymce/EditorManager", [
28714
28714
  } else if (settings.selector) {
28715
28715
  // Process global selector
28716
28716
  each(DOM.select(settings.selector), function(elm) {
28717
- createEditor(createId(elm), settings);
28717
+ createEditor(createId(elm), settings, elm);
28718
28718
  });
28719
28719
 
28720
28720
  return;
28721
+ } else if (settings.target) {
28722
+ createEditor(createId(settings.target), settings);
28721
28723
  }
28722
28724
 
28723
28725
  // Fallback to old setting
@@ -28737,7 +28739,7 @@ define("tinymce/EditorManager", [
28737
28739
  if (e.name === v) {
28738
28740
  v = 'mce_editor_' + instanceCounter++;
28739
28741
  DOM.setAttrib(e, 'id', v);
28740
- createEditor(v, settings);
28742
+ createEditor(v, settings, e);
28741
28743
  }
28742
28744
  });
28743
28745
  });
@@ -28754,7 +28756,7 @@ define("tinymce/EditorManager", [
28754
28756
  }
28755
28757
 
28756
28758
  if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
28757
- createEditor(createId(elm), settings);
28759
+ createEditor(createId(elm), settings, elm);
28758
28760
  }
28759
28761
  });
28760
28762
  break;
@@ -28774,7 +28776,7 @@ define("tinymce/EditorManager", [
28774
28776
 
28775
28777
  // All done
28776
28778
  if (l == co) {
28777
- execCallback(settings, 'oninit');
28779
+ execCallback('oninit');
28778
28780
  }
28779
28781
  });
28780
28782
  } else {
@@ -28783,7 +28785,7 @@ define("tinymce/EditorManager", [
28783
28785
 
28784
28786
  // All done
28785
28787
  if (l == co) {
28786
- execCallback(settings, 'oninit');
28788
+ execCallback('oninit');
28787
28789
  }
28788
28790
  });
28789
28791
  }
@@ -28903,7 +28905,11 @@ define("tinymce/EditorManager", [
28903
28905
  selector = selector.selector || selector;
28904
28906
 
28905
28907
  each(DOM.select(selector), function(elm) {
28906
- self.remove(editors[elm.id]);
28908
+ editor = editors[elm.id];
28909
+
28910
+ if (editor) {
28911
+ self.remove(editor);
28912
+ }
28907
28913
  });
28908
28914
 
28909
28915
  return;
@@ -31814,38 +31820,32 @@ define("tinymce/ui/ElementPath", [
31814
31820
  }
31815
31821
 
31816
31822
  self.on('select', function(e) {
31817
- var parents = [], node, body = editor.getBody();
31818
-
31819
31823
  editor.focus();
31820
-
31821
- node = editor.selection.getStart();
31822
- while (node && node != body) {
31823
- if (!isHidden(node)) {
31824
- parents.push(node);
31825
- }
31826
-
31827
- node = node.parentNode;
31828
- }
31829
-
31830
- editor.selection.select(parents[parents.length - 1 - e.index]);
31824
+ editor.selection.select(this.data()[e.index].element);
31831
31825
  editor.nodeChanged();
31832
31826
  });
31833
31827
 
31834
31828
  editor.on('nodeChange', function(e) {
31835
- var parents = [], selectionParents = e.parents, i = selectionParents.length;
31829
+ var outParents = [], parents = e.parents, i = parents.length;
31836
31830
 
31837
31831
  while (i--) {
31838
- if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) {
31832
+ if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
31839
31833
  var args = editor.fire('ResolveName', {
31840
- name: selectionParents[i].nodeName.toLowerCase(),
31841
- target: selectionParents[i]
31834
+ name: parents[i].nodeName.toLowerCase(),
31835
+ target: parents[i]
31842
31836
  });
31843
31837
 
31844
- parents.push({name: args.name});
31838
+ if (!args.isDefaultPrevented()) {
31839
+ outParents.push({name: args.name, element: parents[i]});
31840
+ }
31841
+
31842
+ if (args.isPropagationStopped()) {
31843
+ break;
31844
+ }
31845
31845
  }
31846
31846
  }
31847
31847
 
31848
- self.data(parents);
31848
+ self.data(outParents);
31849
31849
  });
31850
31850
 
31851
31851
  return self._super();