diff options
Diffstat (limited to 'sources/core/dom/range.js')
-rw-r--r-- | sources/core/dom/range.js | 200 |
1 files changed, 162 insertions, 38 deletions
diff --git a/sources/core/dom/range.js b/sources/core/dom/range.js index 6407074..742c24c 100644 --- a/sources/core/dom/range.js +++ b/sources/core/dom/range.js | |||
@@ -204,9 +204,14 @@ CKEDITOR.dom.range = function( root ) { | |||
204 | // This allows us to not think about startNode == endNode case later on. | 204 | // This allows us to not think about startNode == endNode case later on. |
205 | // We do that only when cloning, because in other cases we can safely split this text node | 205 | // We do that only when cloning, because in other cases we can safely split this text node |
206 | // and hence we can easily handle this case as many others. | 206 | // and hence we can easily handle this case as many others. |
207 | if ( isClone && endNode.type == CKEDITOR.NODE_TEXT && startNode.equals( endNode ) ) { | 207 | |
208 | startNode = range.document.createText( startNode.substring( startOffset, endOffset ) ); | 208 | // We need to handle situation when selection startNode is type of NODE_ELEMENT (#426). |
209 | docFrag.append( startNode ); | 209 | if ( isClone && |
210 | endNode.type == CKEDITOR.NODE_TEXT && | ||
211 | ( startNode.equals( endNode ) || ( startNode.type === CKEDITOR.NODE_ELEMENT && startNode.getFirst().equals( endNode ) ) ) ) { | ||
212 | |||
213 | // Here we should always be inside one text node. | ||
214 | docFrag.append( range.document.createText( endNode.substring( startOffset, endOffset ) ) ); | ||
210 | return; | 215 | return; |
211 | } | 216 | } |
212 | 217 | ||
@@ -214,7 +219,7 @@ CKEDITOR.dom.range = function( root ) { | |||
214 | // second part. The removal will be handled by the rest of the code. | 219 | // second part. The removal will be handled by the rest of the code. |
215 | if ( endNode.type == CKEDITOR.NODE_TEXT ) { | 220 | if ( endNode.type == CKEDITOR.NODE_TEXT ) { |
216 | // If Extract or Delete we can split the text node, | 221 | // If Extract or Delete we can split the text node, |
217 | // but if Clone (2), then we cannot modify the DOM (#11586) so we mark the text node for cloning. | 222 | // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark the text node for cloning. |
218 | if ( !isClone ) { | 223 | if ( !isClone ) { |
219 | endNode = endNode.split( endOffset ); | 224 | endNode = endNode.split( endOffset ); |
220 | } else { | 225 | } else { |
@@ -243,7 +248,7 @@ CKEDITOR.dom.range = function( root ) { | |||
243 | // be handled by the rest of the code . | 248 | // be handled by the rest of the code . |
244 | if ( startNode.type == CKEDITOR.NODE_TEXT ) { | 249 | if ( startNode.type == CKEDITOR.NODE_TEXT ) { |
245 | // If Extract or Delete we can split the text node, | 250 | // If Extract or Delete we can split the text node, |
246 | // but if Clone (2), then we cannot modify the DOM (#11586) so we mark | 251 | // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark |
247 | // the text node for cloning. | 252 | // the text node for cloning. |
248 | if ( !isClone ) { | 253 | if ( !isClone ) { |
249 | startNode.split( startOffset ); | 254 | startNode.split( startOffset ); |
@@ -373,7 +378,7 @@ CKEDITOR.dom.range = function( root ) { | |||
373 | // If we don't do that, in next iterations nodes will be appended to wrong parent. | 378 | // If we don't do that, in next iterations nodes will be appended to wrong parent. |
374 | // | 379 | // |
375 | // We can just take first child because the algorithm guarantees | 380 | // We can just take first child because the algorithm guarantees |
376 | // that this will be the only child on this level. (#13568) | 381 | // that this will be the only child on this level. (http://dev.ckeditor.com/ticket/13568) |
377 | levelParent = levelParent.getChild( 0 ); | 382 | levelParent = levelParent.getChild( 0 ); |
378 | } | 383 | } |
379 | } | 384 | } |
@@ -405,7 +410,7 @@ CKEDITOR.dom.range = function( root ) { | |||
405 | 410 | ||
406 | // When Extracting, move the removed node to the docFrag. | 411 | // When Extracting, move the removed node to the docFrag. |
407 | if ( isExtract ) { | 412 | if ( isExtract ) { |
408 | newParent.append( node ); | 413 | newParent.append( node, toStart ); |
409 | } | 414 | } |
410 | } | 415 | } |
411 | 416 | ||
@@ -574,7 +579,7 @@ CKEDITOR.dom.range = function( root ) { | |||
574 | // Tolerant bogus br when checking at the end of block. | 579 | // Tolerant bogus br when checking at the end of block. |
575 | // Reject any text node unless it's being bookmark | 580 | // Reject any text node unless it's being bookmark |
576 | // OR it's spaces. | 581 | // OR it's spaces. |
577 | // Reject any element unless it's being invisible empty. (#3883) | 582 | // Reject any element unless it's being invisible empty. (http://dev.ckeditor.com/ticket/3883) |
578 | return !checkStart && isBogus( node ) || | 583 | return !checkStart && isBogus( node ) || |
579 | node.type == CKEDITOR.NODE_ELEMENT && | 584 | node.type == CKEDITOR.NODE_ELEMENT && |
580 | node.is( CKEDITOR.dtd.$removeEmpty ); | 585 | node.is( CKEDITOR.dtd.$removeEmpty ); |
@@ -1052,7 +1057,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1052 | } | 1057 | } |
1053 | 1058 | ||
1054 | // Sometimes the endNode will come right before startNode for collapsed | 1059 | // Sometimes the endNode will come right before startNode for collapsed |
1055 | // ranges. Fix it. (#3780) | 1060 | // ranges. Fix it. (http://dev.ckeditor.com/ticket/3780) |
1056 | if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) | 1061 | if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) |
1057 | startNode = endNode; | 1062 | startNode = endNode; |
1058 | 1063 | ||
@@ -1324,13 +1329,13 @@ CKEDITOR.dom.range = function( root ) { | |||
1324 | 1329 | ||
1325 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); | 1330 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); |
1326 | } else { | 1331 | } else { |
1327 | // #12221 (Chrome) plus #11111 (Safari). | 1332 | // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari). |
1328 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; | 1333 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; |
1329 | 1334 | ||
1330 | // If this is a visible element. | 1335 | // If this is a visible element. |
1331 | // We need to check for the bookmark attribute because IE insists on | 1336 | // We need to check for the bookmark attribute because IE insists on |
1332 | // rendering the display:none nodes we use for bookmarks. (#3363) | 1337 | // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363) |
1333 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) | 1338 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041) |
1334 | if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { | 1339 | if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { |
1335 | // We'll accept it only if we need | 1340 | // We'll accept it only if we need |
1336 | // whitespace, and this is an inline | 1341 | // whitespace, and this is an inline |
@@ -1399,7 +1404,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1399 | 1404 | ||
1400 | // Process the end boundary. This is basically the same | 1405 | // Process the end boundary. This is basically the same |
1401 | // code used for the start boundary, with small changes to | 1406 | // code used for the start boundary, with small changes to |
1402 | // make it work in the oposite side (to the right). This | 1407 | // make it work in the opposite side (to the right). This |
1403 | // makes it difficult to reuse the code here. So, fixes to | 1408 | // makes it difficult to reuse the code here. So, fixes to |
1404 | // the above code are likely to be replicated here. | 1409 | // the above code are likely to be replicated here. |
1405 | 1410 | ||
@@ -1482,7 +1487,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1482 | } | 1487 | } |
1483 | } | 1488 | } |
1484 | } else { | 1489 | } else { |
1485 | // Get the node right after the boudary to be checked | 1490 | // Get the node right after the boundary to be checked |
1486 | // first. | 1491 | // first. |
1487 | sibling = container.getChild( offset ); | 1492 | sibling = container.getChild( offset ); |
1488 | 1493 | ||
@@ -1525,8 +1530,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1525 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { | 1530 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { |
1526 | // If this is a visible element. | 1531 | // If this is a visible element. |
1527 | // We need to check for the bookmark attribute because IE insists on | 1532 | // We need to check for the bookmark attribute because IE insists on |
1528 | // rendering the display:none nodes we use for bookmarks. (#3363) | 1533 | // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363) |
1529 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) | 1534 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041) |
1530 | if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { | 1535 | if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { |
1531 | // We'll accept it only if we need | 1536 | // We'll accept it only if we need |
1532 | // whitespace, and this is an inline | 1537 | // whitespace, and this is an inline |
@@ -1617,7 +1622,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1617 | boundaryGuard = function( node ) { | 1622 | boundaryGuard = function( node ) { |
1618 | // We should not check contents of non-editable elements. It may happen | 1623 | // We should not check contents of non-editable elements. It may happen |
1619 | // that inline widget has display:table child which should not block range#enlarge. | 1624 | // that inline widget has display:table child which should not block range#enlarge. |
1620 | // When encoutered non-editable element... | 1625 | // When encountered non-editable element... |
1621 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { | 1626 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { |
1622 | if ( inNonEditable ) { | 1627 | if ( inNonEditable ) { |
1623 | // ... in which we already were, reset it (because we're leaving it) and return. | 1628 | // ... in which we already were, reset it (because we're leaving it) and return. |
@@ -1639,7 +1644,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1639 | blockBoundary = node; | 1644 | blockBoundary = node; |
1640 | return retval; | 1645 | return retval; |
1641 | }, | 1646 | }, |
1642 | // Record the encounted 'tailBr' for later use. | 1647 | // Record the encountered 'tailBr' for later use. |
1643 | tailBrGuard = function( node ) { | 1648 | tailBrGuard = function( node ) { |
1644 | var retval = boundaryGuard( node ); | 1649 | var retval = boundaryGuard( node ); |
1645 | if ( !retval && node.is && node.is( 'br' ) ) | 1650 | if ( !retval && node.is && node.is( 'br' ) ) |
@@ -1660,7 +1665,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1660 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || | 1665 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || |
1661 | enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END ); | 1666 | enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END ); |
1662 | 1667 | ||
1663 | // Avoid enlarging the range further when end boundary spans right after the BR. (#7490) | 1668 | // Avoid enlarging the range further when end boundary spans right after the BR. (http://dev.ckeditor.com/ticket/7490) |
1664 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { | 1669 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { |
1665 | var theRange = this.clone(); | 1670 | var theRange = this.clone(); |
1666 | walker = new CKEDITOR.dom.walker( theRange ); | 1671 | walker = new CKEDITOR.dom.walker( theRange ); |
@@ -1715,18 +1720,28 @@ CKEDITOR.dom.range = function( root ) { | |||
1715 | }, | 1720 | }, |
1716 | 1721 | ||
1717 | /** | 1722 | /** |
1718 | * Descrease the range to make sure that boundaries | 1723 | * Decreases the range to make sure that boundaries |
1719 | * always anchor beside text nodes or innermost element. | 1724 | * always anchor beside text nodes or the innermost element. |
1720 | * | 1725 | * |
1721 | * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}). | 1726 | * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}). |
1722 | * | 1727 | * |
1723 | * * {@link CKEDITOR#SHRINK_ELEMENT} - Shrink the range boundaries to the edge of the innermost element. | 1728 | * * {@link CKEDITOR#SHRINK_ELEMENT} – Shrinks the range boundaries to the edge of the innermost element. |
1724 | * * {@link CKEDITOR#SHRINK_TEXT} - Shrink the range boudaries to anchor by the side of enclosed text | 1729 | * * {@link CKEDITOR#SHRINK_TEXT} – Shrinks the range boundaries to anchor by the side of enclosed text |
1725 | * node, range remains if there's no text nodes on boundaries at all. | 1730 | * node. The range remains if there are no text nodes available on boundaries. |
1726 | * | 1731 | * |
1727 | * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node. | 1732 | * @param {Boolean} [selectContents=false] Whether the resulting range anchors at the inner OR outer boundary of the node. |
1733 | * @param {Boolean/Object} [options=true] If this parameter is of a Boolean type, it is treated as | ||
1734 | * `options.shrinkOnBlockBoundary`. This parameter was added in 4.7.0. | ||
1735 | * @param {Boolean} [options.shrinkOnBlockBoundary=true] Whether the block boundary should be included in | ||
1736 | * the shrunk range. | ||
1737 | * @param {Boolean} [options.skipBogus=false] Whether bogus `<br>` elements should be ignored while | ||
1738 | * `mode` is set to {@link CKEDITOR#SHRINK_TEXT}. This option was added in 4.7.0. | ||
1728 | */ | 1739 | */ |
1729 | shrink: function( mode, selectContents, shrinkOnBlockBoundary ) { | 1740 | shrink: function( mode, selectContents, options ) { |
1741 | var shrinkOnBlockBoundary = typeof options === 'boolean' ? options : | ||
1742 | ( options && typeof options.shrinkOnBlockBoundary === 'boolean' ? options.shrinkOnBlockBoundary : true ), | ||
1743 | skipBogus = options && options.skipBogus; | ||
1744 | |||
1730 | // Unable to shrink a collapsed range. | 1745 | // Unable to shrink a collapsed range. |
1731 | if ( !this.collapsed ) { | 1746 | if ( !this.collapsed ) { |
1732 | mode = mode || CKEDITOR.SHRINK_TEXT; | 1747 | mode = mode || CKEDITOR.SHRINK_TEXT; |
@@ -1749,7 +1764,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1749 | walkerRange.setStartAfter( startContainer ); | 1764 | walkerRange.setStartAfter( startContainer ); |
1750 | else { | 1765 | else { |
1751 | // Enlarge the range properly to avoid walker making | 1766 | // Enlarge the range properly to avoid walker making |
1752 | // DOM changes caused by triming the text nodes later. | 1767 | // DOM changes caused by trimming the text nodes later. |
1753 | walkerRange.setStartBefore( startContainer ); | 1768 | walkerRange.setStartBefore( startContainer ); |
1754 | moveStart = 0; | 1769 | moveStart = 0; |
1755 | } | 1770 | } |
@@ -1767,7 +1782,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1767 | } | 1782 | } |
1768 | 1783 | ||
1769 | var walker = new CKEDITOR.dom.walker( walkerRange ), | 1784 | var walker = new CKEDITOR.dom.walker( walkerRange ), |
1770 | isBookmark = CKEDITOR.dom.walker.bookmark(); | 1785 | isBookmark = CKEDITOR.dom.walker.bookmark(), |
1786 | isBogus = CKEDITOR.dom.walker.bogus(); | ||
1771 | 1787 | ||
1772 | walker.evaluator = function( node ) { | 1788 | walker.evaluator = function( node ) { |
1773 | return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); | 1789 | return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); |
@@ -1775,6 +1791,11 @@ CKEDITOR.dom.range = function( root ) { | |||
1775 | 1791 | ||
1776 | var currentElement; | 1792 | var currentElement; |
1777 | walker.guard = function( node, movingOut ) { | 1793 | walker.guard = function( node, movingOut ) { |
1794 | // Skipping bogus before other cases (http://dev.ckeditor.com/ticket/17010). | ||
1795 | if ( skipBogus && isBogus( node ) ) { | ||
1796 | return true; | ||
1797 | } | ||
1798 | |||
1778 | if ( isBookmark( node ) ) | 1799 | if ( isBookmark( node ) ) |
1779 | return true; | 1800 | return true; |
1780 | 1801 | ||
@@ -1816,7 +1837,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1816 | 1837 | ||
1817 | /** | 1838 | /** |
1818 | * Inserts a node at the start of the range. The range will be expanded | 1839 | * Inserts a node at the start of the range. The range will be expanded |
1819 | * the contain the node. | 1840 | * to contain the node. |
1820 | * | 1841 | * |
1821 | * @param {CKEDITOR.dom.node} node | 1842 | * @param {CKEDITOR.dom.node} node |
1822 | */ | 1843 | */ |
@@ -1843,7 +1864,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1843 | }, | 1864 | }, |
1844 | 1865 | ||
1845 | /** | 1866 | /** |
1846 | * Moves the range to given position according to specified node. | 1867 | * Moves the range to a given position according to the specified node. |
1847 | * | 1868 | * |
1848 | * // HTML: <p>Foo <b>bar</b></p> | 1869 | * // HTML: <p>Foo <b>bar</b></p> |
1849 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); | 1870 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); |
@@ -1851,7 +1872,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1851 | * | 1872 | * |
1852 | * See also {@link #setStartAt} and {@link #setEndAt}. | 1873 | * See also {@link #setStartAt} and {@link #setEndAt}. |
1853 | * | 1874 | * |
1854 | * @param {CKEDITOR.dom.node} node The node according to which position will be set. | 1875 | * @param {CKEDITOR.dom.node} node The node according to which the position will be set. |
1855 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, | 1876 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, |
1856 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, | 1877 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, |
1857 | * {@link CKEDITOR#POSITION_AFTER_END}. | 1878 | * {@link CKEDITOR#POSITION_AFTER_END}. |
@@ -2118,7 +2139,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2118 | // So even if the initial range was placed before the bogus <br>, after creating the bookmark it | 2139 | // So even if the initial range was placed before the bogus <br>, after creating the bookmark it |
2119 | // is placed before the bookmark. | 2140 | // is placed before the bookmark. |
2120 | // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case. | 2141 | // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case. |
2121 | // We remove incorrectly placed one and add a brand new one. (#13001) | 2142 | // We remove incorrectly placed one and add a brand new one. (http://dev.ckeditor.com/ticket/13001) |
2122 | var bogus = fixedBlock.getBogus(); | 2143 | var bogus = fixedBlock.getBogus(); |
2123 | if ( bogus ) { | 2144 | if ( bogus ) { |
2124 | bogus.remove(); | 2145 | bogus.remove(); |
@@ -2340,7 +2361,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2340 | this.trim( 0, 1 ); | 2361 | this.trim( 0, 1 ); |
2341 | } | 2362 | } |
2342 | 2363 | ||
2343 | // Antecipate the trim() call here, so the walker will not make | 2364 | // Anticipate the trim() call here, so the walker will not make |
2344 | // changes to the DOM, which would not get reflected into this | 2365 | // changes to the DOM, which would not get reflected into this |
2345 | // range otherwise. | 2366 | // range otherwise. |
2346 | this.trim(); | 2367 | this.trim(); |
@@ -2379,7 +2400,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2379 | this.trim( 1, 0 ); | 2400 | this.trim( 1, 0 ); |
2380 | } | 2401 | } |
2381 | 2402 | ||
2382 | // Antecipate the trim() call here, so the walker will not make | 2403 | // Anticipate the trim() call here, so the walker will not make |
2383 | // changes to the DOM, which would not get reflected into this | 2404 | // changes to the DOM, which would not get reflected into this |
2384 | // range otherwise. | 2405 | // range otherwise. |
2385 | this.trim(); | 2406 | this.trim(); |
@@ -2636,7 +2657,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2636 | getEnclosedNode: function() { | 2657 | getEnclosedNode: function() { |
2637 | var walkerRange = this.clone(); | 2658 | var walkerRange = this.clone(); |
2638 | 2659 | ||
2639 | // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780) | 2660 | // Optimize and analyze the range to avoid DOM destructive nature of walker. (http://dev.ckeditor.com/ticket/5780) |
2640 | walkerRange.optimize(); | 2661 | walkerRange.optimize(); |
2641 | if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) | 2662 | if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) |
2642 | return null; | 2663 | return null; |
@@ -2703,6 +2724,53 @@ CKEDITOR.dom.range = function( root ) { | |||
2703 | getPreviousEditableNode: getNextEditableNode( 1 ), | 2724 | getPreviousEditableNode: getNextEditableNode( 1 ), |
2704 | 2725 | ||
2705 | /** | 2726 | /** |
2727 | * Returns any table element, like `td`, `tbody`, `table` etc. from a given range. The element | ||
2728 | * is returned only if the range is contained within one table (might be a nested | ||
2729 | * table, but it cannot be two different tables on the same DOM level). | ||
2730 | * | ||
2731 | * @private | ||
2732 | * @since 4.7 | ||
2733 | * @param {Object} [tableElements] Mapping of element names that should be considered. | ||
2734 | * @returns {CKEDITOR.dom.element/null} | ||
2735 | */ | ||
2736 | _getTableElement: function( tableElements ) { | ||
2737 | tableElements = tableElements || { | ||
2738 | td: 1, | ||
2739 | th: 1, | ||
2740 | tr: 1, | ||
2741 | tbody: 1, | ||
2742 | thead: 1, | ||
2743 | tfoot: 1, | ||
2744 | table: 1 | ||
2745 | }; | ||
2746 | |||
2747 | var start = this.startContainer, | ||
2748 | end = this.endContainer, | ||
2749 | startTable = start.getAscendant( 'table', true ), | ||
2750 | endTable = end.getAscendant( 'table', true ); | ||
2751 | |||
2752 | // Super weird edge case in Safari: if there is a table with only one cell inside and that cell | ||
2753 | // is selected, then the end boundary of the table is moved into editor's editable. | ||
2754 | // That case is also present when selecting the last cell inside nested table. | ||
2755 | if ( CKEDITOR.env.safari && startTable && end.equals( this.root ) ) { | ||
2756 | return start.getAscendant( tableElements, true ); | ||
2757 | } | ||
2758 | |||
2759 | if ( this.getEnclosedNode() ) { | ||
2760 | return this.getEnclosedNode().getAscendant( tableElements, true ); | ||
2761 | } | ||
2762 | |||
2763 | // Ensure that selection starts and ends in the same table or one of the table is inside the other. | ||
2764 | if ( startTable && endTable && ( startTable.equals( endTable ) || startTable.contains( endTable ) || | ||
2765 | endTable.contains( startTable ) ) ) { | ||
2766 | |||
2767 | return start.getAscendant( tableElements, true ); | ||
2768 | } | ||
2769 | |||
2770 | return null; | ||
2771 | }, | ||
2772 | |||
2773 | /** | ||
2706 | * Scrolls the start of current range into view. | 2774 | * Scrolls the start of current range into view. |
2707 | */ | 2775 | */ |
2708 | scrollIntoView: function() { | 2776 | scrollIntoView: function() { |
@@ -2819,10 +2887,10 @@ CKEDITOR.dom.range = function( root ) { | |||
2819 | continue; | 2887 | continue; |
2820 | } | 2888 | } |
2821 | 2889 | ||
2822 | // It's not enough to get elements from common ancestor, because it migth contain too many matches. | 2890 | // It's not enough to get elements from common ancestor, because it might contain too many matches. |
2823 | // We need to ensure that returned items are between boundary points. | 2891 | // We need to ensure that returned items are between boundary points. |
2824 | isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem ); | 2892 | isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem ); |
2825 | isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ); | 2893 | isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ) || boundaries.endNode.equals( curItem ); |
2826 | 2894 | ||
2827 | if ( isStartGood && isEndGood ) { | 2895 | if ( isStartGood && isEndGood ) { |
2828 | ret.push( curItem ); | 2896 | ret.push( curItem ); |
@@ -2834,6 +2902,62 @@ CKEDITOR.dom.range = function( root ) { | |||
2834 | } | 2902 | } |
2835 | }; | 2903 | }; |
2836 | 2904 | ||
2905 | /** | ||
2906 | * Merges every subsequent range in given set, returning a smaller array of ranges. | ||
2907 | * | ||
2908 | * Note that each range in the returned value will be enlarged with `CKEDITOR.ENLARGE_ELEMENT` value. | ||
2909 | * | ||
2910 | * @since 4.7.0 | ||
2911 | * @static | ||
2912 | * @param {CKEDITOR.dom.range[]} ranges | ||
2913 | * @returns {CKEDITOR.dom.range[]} Set of merged ranges. | ||
2914 | * @member CKEDITOR.dom.range | ||
2915 | */ | ||
2916 | CKEDITOR.dom.range.mergeRanges = function( ranges ) { | ||
2917 | return CKEDITOR.tools.array.reduce( ranges, function( ret, rng ) { | ||
2918 | // Last range ATM. | ||
2919 | var lastRange = ret[ ret.length - 1 ], | ||
2920 | isContinuation = false; | ||
2921 | |||
2922 | // Make a clone, we don't want to modify input. | ||
2923 | rng = rng.clone(); | ||
2924 | rng.enlarge( CKEDITOR.ENLARGE_ELEMENT ); | ||
2925 | |||
2926 | if ( lastRange ) { | ||
2927 | // The trick is to create a range spanning the gap between the two ranges. Then iterate over | ||
2928 | // each node found in this gap. If it contains anything other than whitespace, then it means it | ||
2929 | // is not a continuation. | ||
2930 | var gapRange = new CKEDITOR.dom.range( rng.root ), | ||
2931 | walker = new CKEDITOR.dom.walker( gapRange ), | ||
2932 | isWhitespace = CKEDITOR.dom.walker.whitespaces(), | ||
2933 | nodeInBetween; | ||
2934 | |||
2935 | gapRange.setStart( lastRange.endContainer, lastRange.endOffset ); | ||
2936 | gapRange.setEnd( rng.startContainer, rng.startOffset ); | ||
2937 | |||
2938 | nodeInBetween = walker.next(); | ||
2939 | |||
2940 | while ( isWhitespace( nodeInBetween ) || rng.endContainer.equals( nodeInBetween ) ) { | ||
2941 | // We don't care about whitespaces, and range container. Also we skip the endContainer, | ||
2942 | // as it will also be provided by the iterator (as it visits it's opening tag). | ||
2943 | nodeInBetween = walker.next(); | ||
2944 | } | ||
2945 | |||
2946 | // Simply, if anything has been found there's a content in between the two. | ||
2947 | isContinuation = !nodeInBetween; | ||
2948 | } | ||
2949 | |||
2950 | if ( isContinuation ) { | ||
2951 | // If last range ends, where the current range starts, then let's merge it. | ||
2952 | lastRange.setEnd( rng.endContainer, rng.endOffset ); | ||
2953 | } else { | ||
2954 | // In other case just push cur range into the stack. | ||
2955 | ret.push( rng ); | ||
2956 | } | ||
2957 | |||
2958 | return ret; | ||
2959 | }, [] ); | ||
2960 | }; | ||
2837 | 2961 | ||
2838 | } )(); | 2962 | } )(); |
2839 | 2963 | ||