diff options
Diffstat (limited to 'sources/core/dom/range.js')
-rw-r--r-- | sources/core/dom/range.js | 271 |
1 files changed, 233 insertions, 38 deletions
diff --git a/sources/core/dom/range.js b/sources/core/dom/range.js index b5e8736..742c24c 100644 --- a/sources/core/dom/range.js +++ b/sources/core/dom/range.js | |||
@@ -1,5 +1,5 @@ | |||
1 | /** | 1 | /** |
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. | 2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license |
4 | */ | 4 | */ |
5 | 5 | ||
@@ -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 | ||
@@ -1201,7 +1206,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1201 | /** | 1206 | /** |
1202 | * Expands the range so that partial units are completely contained. | 1207 | * Expands the range so that partial units are completely contained. |
1203 | * | 1208 | * |
1204 | * @param unit {Number} The unit type to expand with. | 1209 | * @param {Number} unit The unit type to expand with. Use one of following values: {@link CKEDITOR#ENLARGE_BLOCK_CONTENTS}, |
1210 | * {@link CKEDITOR#ENLARGE_ELEMENT}, {@link CKEDITOR#ENLARGE_INLINE}, {@link CKEDITOR#ENLARGE_LIST_ITEM_CONTENTS}. | ||
1205 | * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding. | 1211 | * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding. |
1206 | */ | 1212 | */ |
1207 | enlarge: function( unit, excludeBrs ) { | 1213 | enlarge: function( unit, excludeBrs ) { |
@@ -1323,13 +1329,13 @@ CKEDITOR.dom.range = function( root ) { | |||
1323 | 1329 | ||
1324 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); | 1330 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); |
1325 | } else { | 1331 | } else { |
1326 | // #12221 (Chrome) plus #11111 (Safari). | 1332 | // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari). |
1327 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; | 1333 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; |
1328 | 1334 | ||
1329 | // If this is a visible element. | 1335 | // If this is a visible element. |
1330 | // 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 |
1331 | // 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) |
1332 | // 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) |
1333 | 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' ) ) { |
1334 | // We'll accept it only if we need | 1340 | // We'll accept it only if we need |
1335 | // whitespace, and this is an inline | 1341 | // whitespace, and this is an inline |
@@ -1398,7 +1404,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1398 | 1404 | ||
1399 | // Process the end boundary. This is basically the same | 1405 | // Process the end boundary. This is basically the same |
1400 | // code used for the start boundary, with small changes to | 1406 | // code used for the start boundary, with small changes to |
1401 | // make it work in the oposite side (to the right). This | 1407 | // make it work in the opposite side (to the right). This |
1402 | // makes it difficult to reuse the code here. So, fixes to | 1408 | // makes it difficult to reuse the code here. So, fixes to |
1403 | // the above code are likely to be replicated here. | 1409 | // the above code are likely to be replicated here. |
1404 | 1410 | ||
@@ -1481,7 +1487,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1481 | } | 1487 | } |
1482 | } | 1488 | } |
1483 | } else { | 1489 | } else { |
1484 | // Get the node right after the boudary to be checked | 1490 | // Get the node right after the boundary to be checked |
1485 | // first. | 1491 | // first. |
1486 | sibling = container.getChild( offset ); | 1492 | sibling = container.getChild( offset ); |
1487 | 1493 | ||
@@ -1524,8 +1530,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1524 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { | 1530 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { |
1525 | // If this is a visible element. | 1531 | // If this is a visible element. |
1526 | // 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 |
1527 | // 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) |
1528 | // 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) |
1529 | 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' ) ) { |
1530 | // We'll accept it only if we need | 1536 | // We'll accept it only if we need |
1531 | // whitespace, and this is an inline | 1537 | // whitespace, and this is an inline |
@@ -1616,7 +1622,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1616 | boundaryGuard = function( node ) { | 1622 | boundaryGuard = function( node ) { |
1617 | // 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 |
1618 | // 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. |
1619 | // When encoutered non-editable element... | 1625 | // When encountered non-editable element... |
1620 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { | 1626 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { |
1621 | if ( inNonEditable ) { | 1627 | if ( inNonEditable ) { |
1622 | // ... 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. |
@@ -1638,7 +1644,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1638 | blockBoundary = node; | 1644 | blockBoundary = node; |
1639 | return retval; | 1645 | return retval; |
1640 | }, | 1646 | }, |
1641 | // Record the encounted 'tailBr' for later use. | 1647 | // Record the encountered 'tailBr' for later use. |
1642 | tailBrGuard = function( node ) { | 1648 | tailBrGuard = function( node ) { |
1643 | var retval = boundaryGuard( node ); | 1649 | var retval = boundaryGuard( node ); |
1644 | if ( !retval && node.is && node.is( 'br' ) ) | 1650 | if ( !retval && node.is && node.is( 'br' ) ) |
@@ -1659,7 +1665,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1659 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || | 1665 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || |
1660 | 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 ); |
1661 | 1667 | ||
1662 | // 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) |
1663 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { | 1669 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { |
1664 | var theRange = this.clone(); | 1670 | var theRange = this.clone(); |
1665 | walker = new CKEDITOR.dom.walker( theRange ); | 1671 | walker = new CKEDITOR.dom.walker( theRange ); |
@@ -1714,18 +1720,28 @@ CKEDITOR.dom.range = function( root ) { | |||
1714 | }, | 1720 | }, |
1715 | 1721 | ||
1716 | /** | 1722 | /** |
1717 | * Descrease the range to make sure that boundaries | 1723 | * Decreases the range to make sure that boundaries |
1718 | * always anchor beside text nodes or innermost element. | 1724 | * always anchor beside text nodes or the innermost element. |
1719 | * | 1725 | * |
1720 | * @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}). |
1721 | * | 1727 | * |
1722 | * * {@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. |
1723 | * * {@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 |
1724 | * 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. |
1725 | * | 1731 | * |
1726 | * @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. | ||
1727 | */ | 1739 | */ |
1728 | 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 | |||
1729 | // Unable to shrink a collapsed range. | 1745 | // Unable to shrink a collapsed range. |
1730 | if ( !this.collapsed ) { | 1746 | if ( !this.collapsed ) { |
1731 | mode = mode || CKEDITOR.SHRINK_TEXT; | 1747 | mode = mode || CKEDITOR.SHRINK_TEXT; |
@@ -1748,7 +1764,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1748 | walkerRange.setStartAfter( startContainer ); | 1764 | walkerRange.setStartAfter( startContainer ); |
1749 | else { | 1765 | else { |
1750 | // Enlarge the range properly to avoid walker making | 1766 | // Enlarge the range properly to avoid walker making |
1751 | // DOM changes caused by triming the text nodes later. | 1767 | // DOM changes caused by trimming the text nodes later. |
1752 | walkerRange.setStartBefore( startContainer ); | 1768 | walkerRange.setStartBefore( startContainer ); |
1753 | moveStart = 0; | 1769 | moveStart = 0; |
1754 | } | 1770 | } |
@@ -1766,7 +1782,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1766 | } | 1782 | } |
1767 | 1783 | ||
1768 | var walker = new CKEDITOR.dom.walker( walkerRange ), | 1784 | var walker = new CKEDITOR.dom.walker( walkerRange ), |
1769 | isBookmark = CKEDITOR.dom.walker.bookmark(); | 1785 | isBookmark = CKEDITOR.dom.walker.bookmark(), |
1786 | isBogus = CKEDITOR.dom.walker.bogus(); | ||
1770 | 1787 | ||
1771 | walker.evaluator = function( node ) { | 1788 | walker.evaluator = function( node ) { |
1772 | 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 ); |
@@ -1774,6 +1791,11 @@ CKEDITOR.dom.range = function( root ) { | |||
1774 | 1791 | ||
1775 | var currentElement; | 1792 | var currentElement; |
1776 | 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 | |||
1777 | if ( isBookmark( node ) ) | 1799 | if ( isBookmark( node ) ) |
1778 | return true; | 1800 | return true; |
1779 | 1801 | ||
@@ -1815,7 +1837,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1815 | 1837 | ||
1816 | /** | 1838 | /** |
1817 | * 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 |
1818 | * the contain the node. | 1840 | * to contain the node. |
1819 | * | 1841 | * |
1820 | * @param {CKEDITOR.dom.node} node | 1842 | * @param {CKEDITOR.dom.node} node |
1821 | */ | 1843 | */ |
@@ -1842,7 +1864,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1842 | }, | 1864 | }, |
1843 | 1865 | ||
1844 | /** | 1866 | /** |
1845 | * Moves the range to given position according to specified node. | 1867 | * Moves the range to a given position according to the specified node. |
1846 | * | 1868 | * |
1847 | * // HTML: <p>Foo <b>bar</b></p> | 1869 | * // HTML: <p>Foo <b>bar</b></p> |
1848 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); | 1870 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); |
@@ -1850,7 +1872,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1850 | * | 1872 | * |
1851 | * See also {@link #setStartAt} and {@link #setEndAt}. | 1873 | * See also {@link #setStartAt} and {@link #setEndAt}. |
1852 | * | 1874 | * |
1853 | * @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. |
1854 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, | 1876 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, |
1855 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, | 1877 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, |
1856 | * {@link CKEDITOR#POSITION_AFTER_END}. | 1878 | * {@link CKEDITOR#POSITION_AFTER_END}. |
@@ -2117,7 +2139,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2117 | // 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 |
2118 | // is placed before the bookmark. | 2140 | // is placed before the bookmark. |
2119 | // 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. |
2120 | // 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) |
2121 | var bogus = fixedBlock.getBogus(); | 2143 | var bogus = fixedBlock.getBogus(); |
2122 | if ( bogus ) { | 2144 | if ( bogus ) { |
2123 | bogus.remove(); | 2145 | bogus.remove(); |
@@ -2339,7 +2361,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2339 | this.trim( 0, 1 ); | 2361 | this.trim( 0, 1 ); |
2340 | } | 2362 | } |
2341 | 2363 | ||
2342 | // Antecipate the trim() call here, so the walker will not make | 2364 | // Anticipate the trim() call here, so the walker will not make |
2343 | // changes to the DOM, which would not get reflected into this | 2365 | // changes to the DOM, which would not get reflected into this |
2344 | // range otherwise. | 2366 | // range otherwise. |
2345 | this.trim(); | 2367 | this.trim(); |
@@ -2378,7 +2400,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2378 | this.trim( 1, 0 ); | 2400 | this.trim( 1, 0 ); |
2379 | } | 2401 | } |
2380 | 2402 | ||
2381 | // Antecipate the trim() call here, so the walker will not make | 2403 | // Anticipate the trim() call here, so the walker will not make |
2382 | // changes to the DOM, which would not get reflected into this | 2404 | // changes to the DOM, which would not get reflected into this |
2383 | // range otherwise. | 2405 | // range otherwise. |
2384 | this.trim(); | 2406 | this.trim(); |
@@ -2635,7 +2657,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2635 | getEnclosedNode: function() { | 2657 | getEnclosedNode: function() { |
2636 | var walkerRange = this.clone(); | 2658 | var walkerRange = this.clone(); |
2637 | 2659 | ||
2638 | // 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) |
2639 | walkerRange.optimize(); | 2661 | walkerRange.optimize(); |
2640 | 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 ) |
2641 | return null; | 2663 | return null; |
@@ -2702,6 +2724,53 @@ CKEDITOR.dom.range = function( root ) { | |||
2702 | getPreviousEditableNode: getNextEditableNode( 1 ), | 2724 | getPreviousEditableNode: getNextEditableNode( 1 ), |
2703 | 2725 | ||
2704 | /** | 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 | /** | ||
2705 | * Scrolls the start of current range into view. | 2774 | * Scrolls the start of current range into view. |
2706 | */ | 2775 | */ |
2707 | scrollIntoView: function() { | 2776 | scrollIntoView: function() { |
@@ -2783,9 +2852,112 @@ CKEDITOR.dom.range = function( root ) { | |||
2783 | } | 2852 | } |
2784 | // %REMOVE_END% | 2853 | // %REMOVE_END% |
2785 | this.endContainer = endContainer; | 2854 | this.endContainer = endContainer; |
2855 | }, | ||
2856 | |||
2857 | /** | ||
2858 | * Looks for elements matching the `query` selector within a range. | ||
2859 | * | ||
2860 | * @since 4.5.11 | ||
2861 | * @private | ||
2862 | * @param {String} query | ||
2863 | * @param {Boolean} [includeNonEditables=false] Whether elements with `contenteditable` set to `false` should | ||
2864 | * be included. | ||
2865 | * @returns {CKEDITOR.dom.element[]} | ||
2866 | */ | ||
2867 | _find: function( query, includeNonEditables ) { | ||
2868 | var ancestor = this.getCommonAncestor(), | ||
2869 | boundaries = this.getBoundaryNodes(), | ||
2870 | // Contrary to CKEDITOR.dom.element#find we're returning array, that's because NodeList is immutable, and we need | ||
2871 | // to do some filtering in returned list. | ||
2872 | ret = [], | ||
2873 | curItem, | ||
2874 | i, | ||
2875 | initialMatches, | ||
2876 | isStartGood, | ||
2877 | isEndGood; | ||
2878 | |||
2879 | if ( ancestor && ancestor.find ) { | ||
2880 | initialMatches = ancestor.find( query ); | ||
2881 | |||
2882 | for ( i = 0; i < initialMatches.count(); i++ ) { | ||
2883 | curItem = initialMatches.getItem( i ); | ||
2884 | |||
2885 | // Using isReadOnly() method to filterout non editables. It checks isContentEditable including all browser quirks. | ||
2886 | if ( !includeNonEditables && curItem.isReadOnly() ) { | ||
2887 | continue; | ||
2888 | } | ||
2889 | |||
2890 | // It's not enough to get elements from common ancestor, because it might contain too many matches. | ||
2891 | // We need to ensure that returned items are between boundary points. | ||
2892 | isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem ); | ||
2893 | isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ) || boundaries.endNode.equals( curItem ); | ||
2894 | |||
2895 | if ( isStartGood && isEndGood ) { | ||
2896 | ret.push( curItem ); | ||
2897 | } | ||
2898 | } | ||
2899 | } | ||
2900 | |||
2901 | return ret; | ||
2786 | } | 2902 | } |
2787 | }; | 2903 | }; |
2788 | 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 | }; | ||
2789 | 2961 | ||
2790 | } )(); | 2962 | } )(); |
2791 | 2963 | ||
@@ -2861,9 +3033,32 @@ CKEDITOR.POSITION_BEFORE_START = 3; | |||
2861 | */ | 3033 | */ |
2862 | CKEDITOR.POSITION_AFTER_END = 4; | 3034 | CKEDITOR.POSITION_AFTER_END = 4; |
2863 | 3035 | ||
3036 | /** | ||
3037 | * @readonly | ||
3038 | * @member CKEDITOR | ||
3039 | * @property {Number} [=1] | ||
3040 | */ | ||
2864 | CKEDITOR.ENLARGE_ELEMENT = 1; | 3041 | CKEDITOR.ENLARGE_ELEMENT = 1; |
3042 | |||
3043 | /** | ||
3044 | * @readonly | ||
3045 | * @member CKEDITOR | ||
3046 | * @property {Number} [=2] | ||
3047 | */ | ||
2865 | CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; | 3048 | CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; |
3049 | |||
3050 | /** | ||
3051 | * @readonly | ||
3052 | * @member CKEDITOR | ||
3053 | * @property {Number} [=3] | ||
3054 | */ | ||
2866 | CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; | 3055 | CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; |
3056 | |||
3057 | /** | ||
3058 | * @readonly | ||
3059 | * @member CKEDITOR | ||
3060 | * @property {Number} [=4] | ||
3061 | */ | ||
2867 | CKEDITOR.ENLARGE_INLINE = 4; | 3062 | CKEDITOR.ENLARGE_INLINE = 4; |
2868 | 3063 | ||
2869 | // Check boundary types. | 3064 | // Check boundary types. |