aboutsummaryrefslogtreecommitdiff
path: root/sources/core/dom/range.js
diff options
context:
space:
mode:
Diffstat (limited to 'sources/core/dom/range.js')
-rw-r--r--sources/core/dom/range.js271
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 */
2862CKEDITOR.POSITION_AFTER_END = 4; 3034CKEDITOR.POSITION_AFTER_END = 4;
2863 3035
3036/**
3037 * @readonly
3038 * @member CKEDITOR
3039 * @property {Number} [=1]
3040 */
2864CKEDITOR.ENLARGE_ELEMENT = 1; 3041CKEDITOR.ENLARGE_ELEMENT = 1;
3042
3043/**
3044 * @readonly
3045 * @member CKEDITOR
3046 * @property {Number} [=2]
3047 */
2865CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; 3048CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
3049
3050/**
3051 * @readonly
3052 * @member CKEDITOR
3053 * @property {Number} [=3]
3054 */
2866CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; 3055CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
3056
3057/**
3058 * @readonly
3059 * @member CKEDITOR
3060 * @property {Number} [=4]
3061 */
2867CKEDITOR.ENLARGE_INLINE = 4; 3062CKEDITOR.ENLARGE_INLINE = 4;
2868 3063
2869// Check boundary types. 3064// Check boundary types.