tinymce-rails 4.1.0 → 4.1.2

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