diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2017-12-04 17:54:04 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2017-12-04 17:54:04 +0100 |
commit | 317f8f8f0651488f226b5280a8f036c7c135c639 (patch) | |
tree | 97bd4889ab2822a00d4b0f0d5cde38b59f9f41de /sources/core/selection.js | |
parent | 1096cdefb1c9a3f3c4ca6807e272da6c92e5ed9c (diff) | |
download | piedsjaloux-ckeditor-component-4.7.3.tar.gz piedsjaloux-ckeditor-component-4.7.3.tar.zst piedsjaloux-ckeditor-component-4.7.3.zip |
Add oembed4.7.3
Diffstat (limited to 'sources/core/selection.js')
-rw-r--r-- | sources/core/selection.js | 446 |
1 files changed, 367 insertions, 79 deletions
diff --git a/sources/core/selection.js b/sources/core/selection.js index 573b890..d44db3b 100644 --- a/sources/core/selection.js +++ b/sources/core/selection.js | |||
@@ -1,9 +1,217 @@ | |||
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 | ||
6 | ( function() { | 6 | ( function() { |
7 | var isMSSelection = typeof window.getSelection != 'function', | ||
8 | nextRev = 1, | ||
9 | // http://dev.ckeditor.com/ticket/13816 | ||
10 | fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ), | ||
11 | fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ), | ||
12 | isSelectingTable; | ||
13 | |||
14 | // #### table selection : START | ||
15 | // @param {CKEDITOR.dom.range[]} ranges | ||
16 | // @param {Boolean} allowPartially Whether a collapsed selection within table is recognized to be a valid selection. | ||
17 | // This happens for WebKits on MacOS, when you right click inside the table. | ||
18 | function isTableSelection( ranges, allowPartially ) { | ||
19 | if ( ranges.length === 0 ) { | ||
20 | return false; | ||
21 | } | ||
22 | |||
23 | var node, | ||
24 | i; | ||
25 | |||
26 | function isPartiallySelected( range ) { | ||
27 | var startCell = range.startContainer.getAscendant( { td: 1, th: 1 }, true ), | ||
28 | endCell = range.endContainer.getAscendant( { td: 1, th: 1 }, true ), | ||
29 | trim = CKEDITOR.tools.trim, | ||
30 | selected; | ||
31 | |||
32 | // Check if the selection is inside one cell and we don't have any nested table contents selected. | ||
33 | if ( !startCell || !startCell.equals( endCell ) || startCell.findOne( 'td, th, tr, tbody, table' ) ) { | ||
34 | return false; | ||
35 | } | ||
36 | |||
37 | selected = range.cloneContents(); | ||
38 | |||
39 | // Empty selection is still partially selected. | ||
40 | if ( !selected.getFirst() ) { | ||
41 | return true; | ||
42 | } | ||
43 | |||
44 | return trim( selected.getFirst().getText() ) !== trim( startCell.getText() ); | ||
45 | } | ||
46 | |||
47 | // Edge case: partially selected text node inside one table cell or cursor inside cell. | ||
48 | if ( !allowPartially && ranges.length === 1 && | ||
49 | ( ranges[ 0 ].collapsed || isPartiallySelected( ranges[ 0 ] ) ) ) { | ||
50 | return false; | ||
51 | } | ||
52 | |||
53 | for ( i = 0; i < ranges.length; i++ ) { | ||
54 | node = ranges[ i ]._getTableElement(); | ||
55 | |||
56 | if ( !node ) { | ||
57 | return false; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return true; | ||
62 | } | ||
63 | |||
64 | // After performing fake table selection, the real selection is limited | ||
65 | // to the first selected cell. Therefore to check if the real selection | ||
66 | // matches the fake selection, we check if the table cell from fake selection's | ||
67 | // first range and real selection's range are the same. | ||
68 | // Also if the selection is collapsed, we should check if it's placed inside the table | ||
69 | // in which the fake selection is or inside nested table. Such selection occurs after right mouse click. | ||
70 | function isRealTableSelection( selection, fakeSelection ) { | ||
71 | var ranges = selection.getRanges(), | ||
72 | fakeRanges = fakeSelection.getRanges(), | ||
73 | table = ranges.length && ranges[ 0 ]._getTableElement() && | ||
74 | ranges[ 0 ]._getTableElement().getAscendant( 'table', true ), | ||
75 | fakeTable = fakeRanges.length && fakeRanges[ 0 ]._getTableElement() && | ||
76 | fakeRanges[ 0 ]._getTableElement().getAscendant( 'table', true ), | ||
77 | isTableRange = ranges.length === 1 && ranges[ 0 ]._getTableElement() && | ||
78 | ranges[ 0 ]._getTableElement().is( 'table' ), | ||
79 | isFakeTableRange = fakeRanges.length === 1 && fakeRanges[ 0 ]._getTableElement() && | ||
80 | fakeRanges[ 0 ]._getTableElement().is( 'table' ); | ||
81 | |||
82 | function isValidTableSelection( table, fakeTable, ranges, fakeRanges ) { | ||
83 | var isMenuOpen = ranges.length === 1 && ranges[ 0 ].collapsed, | ||
84 | // In case of WebKit on MacOS, when checking real selection, we must allow selection to be partial. | ||
85 | // Otherwise the check will fail for table selection with opened context menu. | ||
86 | isInTable = isTableSelection( ranges, !!CKEDITOR.env.webkit ) && isTableSelection( fakeRanges ); | ||
87 | |||
88 | return isSameTable( table, fakeTable ) && ( isMenuOpen || isInTable ); | ||
89 | } | ||
90 | |||
91 | function isSameTable( table, fakeTable ) { | ||
92 | if ( !table || !fakeTable ) { | ||
93 | return false; | ||
94 | } | ||
95 | |||
96 | return table.equals( fakeTable ) || fakeTable.contains( table ); | ||
97 | } | ||
98 | |||
99 | if ( isValidTableSelection( table, fakeTable, ranges, fakeRanges ) ) { | ||
100 | // Edge case: when editor contains only table and that table is selected using selectAll command, | ||
101 | // then the selection is not properly refreshed and it must be done manually. | ||
102 | if ( isTableRange && !isFakeTableRange ) { | ||
103 | fakeSelection.selectRanges( ranges ); | ||
104 | } | ||
105 | return true; | ||
106 | } | ||
107 | |||
108 | return false; | ||
109 | } | ||
110 | |||
111 | function getSelectedCells( ranges ) { | ||
112 | var cells = [], | ||
113 | node, | ||
114 | i; | ||
115 | |||
116 | function getCellsFromElement( element ) { | ||
117 | var cells = element.find( 'td, th' ), | ||
118 | cellsArray = [], | ||
119 | i; | ||
120 | |||
121 | for ( i = 0; i < cells.count(); i++ ) { | ||
122 | cellsArray.push( cells.getItem( i ) ); | ||
123 | } | ||
124 | |||
125 | return cellsArray; | ||
126 | } | ||
127 | |||
128 | for ( i = 0; i < ranges.length; i++ ) { | ||
129 | node = ranges[ i ]._getTableElement(); | ||
130 | |||
131 | if ( node.is && node.is( { td: 1, th: 1 } ) ) { | ||
132 | cells.push( node ); | ||
133 | } else { | ||
134 | cells = cells.concat( getCellsFromElement( node ) ); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | return cells; | ||
139 | } | ||
140 | |||
141 | // Cells in the same row are separated by tab and the rows are separated by new line, e.g. | ||
142 | // Cell 1.1 Cell 1.2 | ||
143 | // Cell 2.1 Cell 2.2 | ||
144 | function getTextFromSelectedCells( ranges ) { | ||
145 | var cells = getSelectedCells( ranges ), | ||
146 | txt = '', | ||
147 | currentRow = [], | ||
148 | lastRow, | ||
149 | i; | ||
150 | |||
151 | for ( i = 0; i < cells.length; i++ ) { | ||
152 | if ( lastRow && !lastRow.equals( cells[ i ].getAscendant( 'tr' ) ) ) { | ||
153 | txt += currentRow.join( '\t' ) + '\n'; | ||
154 | lastRow = cells[ i ].getAscendant( 'tr' ); | ||
155 | currentRow = []; | ||
156 | } else if ( i === 0 ) { | ||
157 | lastRow = cells[ i ].getAscendant( 'tr' ); | ||
158 | } | ||
159 | |||
160 | currentRow.push( cells[ i ].getText() ); | ||
161 | } | ||
162 | |||
163 | txt += currentRow.join( '\t' ); | ||
164 | |||
165 | return txt; | ||
166 | } | ||
167 | |||
168 | function performFakeTableSelection( ranges ) { | ||
169 | var editor = this.root.editor, | ||
170 | realSelection = editor.getSelection( 1 ), | ||
171 | cache; | ||
172 | |||
173 | // Cleanup after previous selection - e.g. remove hidden sel container. | ||
174 | this.reset(); | ||
175 | |||
176 | // Indicate that the table is being fake-selected to prevent infinite loop | ||
177 | // inside `selectRanges`. | ||
178 | isSelectingTable = true; | ||
179 | |||
180 | // Cancel selectionchange for the real selection. | ||
181 | realSelection.root.once( 'selectionchange', function( evt ) { | ||
182 | evt.cancel(); | ||
183 | }, null, null, 0 ); | ||
184 | |||
185 | // Move real selection to the first selected range. | ||
186 | realSelection.selectRanges( [ ranges[ 0 ] ] ); | ||
187 | |||
188 | cache = this._.cache; | ||
189 | |||
190 | // Caches given ranges. | ||
191 | cache.ranges = new CKEDITOR.dom.rangeList( ranges ); | ||
192 | cache.type = CKEDITOR.SELECTION_TEXT; | ||
193 | cache.selectedElement = ranges[ 0 ]._getTableElement(); | ||
194 | |||
195 | // `selectedText` should contain text from all selected data ("plain text table") | ||
196 | // to be compatible with Firefox's implementation. | ||
197 | cache.selectedText = getTextFromSelectedCells( ranges ); | ||
198 | |||
199 | // Properties that will not be available when isFake. | ||
200 | cache.nativeSel = null; | ||
201 | |||
202 | this.isFake = 1; | ||
203 | this.rev = nextRev++; | ||
204 | |||
205 | // Save this selection, so it can be returned by editor.getSelection(). | ||
206 | editor._.fakeSelection = this; | ||
207 | |||
208 | isSelectingTable = false; | ||
209 | |||
210 | // Fire selectionchange, just like a normal selection. | ||
211 | this.root.fire( 'selectionchange' ); | ||
212 | } | ||
213 | // #### table selection : END | ||
214 | |||
7 | // #### checkSelectionChange : START | 215 | // #### checkSelectionChange : START |
8 | 216 | ||
9 | // The selection change check basically saves the element parent tree of | 217 | // The selection change check basically saves the element parent tree of |
@@ -16,10 +224,9 @@ | |||
16 | 224 | ||
17 | if ( sel ) { | 225 | if ( sel ) { |
18 | realSel = this.getSelection( 1 ); | 226 | realSel = this.getSelection( 1 ); |
19 | 227 | // If real (not locked/stored) selection was moved from hidden container | |
20 | // If real (not locked/stored) selection was moved from hidden container, | 228 | // or is not a table one, then the fake-selection must be invalidated. |
21 | // then the fake-selection must be invalidated. | 229 | if ( !realSel || ( !realSel.isHidden() && !isRealTableSelection( realSel, sel ) ) ) { |
22 | if ( !realSel || !realSel.isHidden() ) { | ||
23 | // Remove the cache from fake-selection references in use elsewhere. | 230 | // Remove the cache from fake-selection references in use elsewhere. |
24 | sel.reset(); | 231 | sel.reset(); |
25 | 232 | ||
@@ -41,8 +248,10 @@ | |||
41 | 248 | ||
42 | var currentPath = this.elementPath(); | 249 | var currentPath = this.elementPath(); |
43 | if ( !currentPath.compare( this._.selectionPreviousPath ) ) { | 250 | if ( !currentPath.compare( this._.selectionPreviousPath ) ) { |
251 | // Handle case when dialog inserts new element but parent block and path (so also focus context) does not change. (http://dev.ckeditor.com/ticket/13362) | ||
252 | var sameBlockParent = this._.selectionPreviousPath && this._.selectionPreviousPath.blockLimit.equals( currentPath.blockLimit ); | ||
44 | // Cache the active element, which we'll eventually lose on Webkit. | 253 | // Cache the active element, which we'll eventually lose on Webkit. |
45 | if ( CKEDITOR.env.webkit ) | 254 | if ( CKEDITOR.env.webkit && !sameBlockParent ) |
46 | this._.previousActive = this.document.getActive(); | 255 | this._.previousActive = this.document.getActive(); |
47 | 256 | ||
48 | this._.selectionPreviousPath = currentPath; | 257 | this._.selectionPreviousPath = currentPath; |
@@ -89,7 +298,7 @@ | |||
89 | // * is a visible node, | 298 | // * is a visible node, |
90 | // * is a non-empty element (this rule will accept elements like <strong></strong> because they | 299 | // * is a non-empty element (this rule will accept elements like <strong></strong> because they |
91 | // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret). | 300 | // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret). |
92 | // See #12621. | 301 | // See http://dev.ckeditor.com/ticket/12621. |
93 | function mayAbsorbCaret( node ) { | 302 | function mayAbsorbCaret( node ) { |
94 | if ( isVisible( node ) ) | 303 | if ( isVisible( node ) ) |
95 | return true; | 304 | return true; |
@@ -130,8 +339,8 @@ | |||
130 | if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) ) | 339 | if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) ) |
131 | return true; | 340 | return true; |
132 | 341 | ||
133 | // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (#7222) | 342 | // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (http://dev.ckeditor.com/ticket/7222) |
134 | // If you found this line confusing check #12655. | 343 | // If you found this line confusing check http://dev.ckeditor.com/ticket/12655. |
135 | if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) ) | 344 | if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) ) |
136 | return true; | 345 | return true; |
137 | 346 | ||
@@ -147,7 +356,7 @@ | |||
147 | return fillingChar; | 356 | return fillingChar; |
148 | } | 357 | } |
149 | 358 | ||
150 | // Checks if a filling char has been used, eventualy removing it (#1272). | 359 | // Checks if a filling char has been used, eventually removing it (http://dev.ckeditor.com/ticket/1272). |
151 | function checkFillingCharSequenceNodeReady( editable ) { | 360 | function checkFillingCharSequenceNodeReady( editable ) { |
152 | var fillingChar = editable.getCustomData( 'cke-fillingChar' ); | 361 | var fillingChar = editable.getCustomData( 'cke-fillingChar' ); |
153 | 362 | ||
@@ -156,6 +365,7 @@ | |||
156 | // creating it. | 365 | // creating it. |
157 | if ( fillingChar.getCustomData( 'ready' ) ) { | 366 | if ( fillingChar.getCustomData( 'ready' ) ) { |
158 | removeFillingCharSequenceNode( editable ); | 367 | removeFillingCharSequenceNode( editable ); |
368 | editable.editor.fire( 'selectionCheck' ); | ||
159 | } else { | 369 | } else { |
160 | fillingChar.setCustomData( 'ready', 1 ); | 370 | fillingChar.setCustomData( 'ready', 1 ); |
161 | } | 371 | } |
@@ -167,7 +377,7 @@ | |||
167 | 377 | ||
168 | if ( fillingChar ) { | 378 | if ( fillingChar ) { |
169 | // Text selection position might get mangled by | 379 | // Text selection position might get mangled by |
170 | // subsequent dom modification, save it now for restoring. (#8617) | 380 | // subsequent dom modification, save it now for restoring. (http://dev.ckeditor.com/ticket/8617) |
171 | if ( keepSelection !== false ) { | 381 | if ( keepSelection !== false ) { |
172 | var sel = editable.getDocument().getSelection().getNative(), | 382 | var sel = editable.getDocument().getSelection().getNative(), |
173 | // Be error proof. | 383 | // Be error proof. |
@@ -203,11 +413,11 @@ | |||
203 | } | 413 | } |
204 | } | 414 | } |
205 | 415 | ||
206 | // #13816 | 416 | // http://dev.ckeditor.com/ticket/13816 |
207 | function removeFillingCharSequenceString( str, nbspAware ) { | 417 | function removeFillingCharSequenceString( str, nbspAware ) { |
208 | if ( nbspAware ) { | 418 | if ( nbspAware ) { |
209 | return str.replace( fillingCharSequenceRegExp, function( m, p ) { | 419 | return str.replace( fillingCharSequenceRegExp, function( m, p ) { |
210 | // #10291 if filling char is followed by a space replace it with NBSP. | 420 | // http://dev.ckeditor.com/ticket/10291 if filling char is followed by a space replace it with NBSP. |
211 | return p ? '\xa0' : ''; | 421 | return p ? '\xa0' : ''; |
212 | } ); | 422 | } ); |
213 | } else { | 423 | } else { |
@@ -234,10 +444,11 @@ | |||
234 | } | 444 | } |
235 | 445 | ||
236 | // Creates cke_hidden_sel container and puts real selection there. | 446 | // Creates cke_hidden_sel container and puts real selection there. |
237 | function hideSelection( editor ) { | 447 | function hideSelection( editor, ariaLabel ) { |
238 | var style = CKEDITOR.env.ie ? 'display:none' : 'position:fixed;top:0;left:-1000px', | 448 | var content = ariaLabel || ' ', |
449 | style = CKEDITOR.env.ie && CKEDITOR.env.version < 14 ? 'display:none' : 'position:fixed;top:0;left:-1000px', | ||
239 | hiddenEl = CKEDITOR.dom.element.createFromHtml( | 450 | hiddenEl = CKEDITOR.dom.element.createFromHtml( |
240 | '<div data-cke-hidden-sel="1" data-cke-temp="1" style="' + style + '"> </div>', | 451 | '<div data-cke-hidden-sel="1" data-cke-temp="1" style="' + style + '">' + content + '</div>', |
241 | editor.document ); | 452 | editor.document ); |
242 | 453 | ||
243 | editor.fire( 'lockSnapshot' ); | 454 | editor.fire( 'lockSnapshot' ); |
@@ -382,7 +593,7 @@ | |||
382 | ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) { | 593 | ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) { |
383 | // So far we can't say that enclosed element is non-editable. Before checking, | 594 | // So far we can't say that enclosed element is non-editable. Before checking, |
384 | // we'll shrink range (clone). Shrinking will stop on non-editable range, or | 595 | // we'll shrink range (clone). Shrinking will stop on non-editable range, or |
385 | // innermost element (#11114). | 596 | // innermost element (http://dev.ckeditor.com/ticket/11114). |
386 | clone = range.clone(); | 597 | clone = range.clone(); |
387 | clone.shrink( CKEDITOR.SHRINK_ELEMENT, true ); | 598 | clone.shrink( CKEDITOR.SHRINK_ELEMENT, true ); |
388 | 599 | ||
@@ -516,7 +727,7 @@ | |||
516 | 727 | ||
517 | // Give the editable an initial selection on first focus, | 728 | // Give the editable an initial selection on first focus, |
518 | // put selection at a consistent position at the start | 729 | // put selection at a consistent position at the start |
519 | // of the contents. (#9507) | 730 | // of the contents. (http://dev.ckeditor.com/ticket/9507) |
520 | if ( CKEDITOR.env.gecko ) { | 731 | if ( CKEDITOR.env.gecko ) { |
521 | editable.attachListener( editable, 'focus', function( evt ) { | 732 | editable.attachListener( editable, 'focus', function( evt ) { |
522 | evt.removeListener(); | 733 | evt.removeListener(); |
@@ -524,7 +735,7 @@ | |||
524 | if ( restoreSel !== 0 ) { | 735 | if ( restoreSel !== 0 ) { |
525 | var nativ = editor.getSelection().getNative(); | 736 | var nativ = editor.getSelection().getNative(); |
526 | // Do it only if the native selection is at an unwanted | 737 | // Do it only if the native selection is at an unwanted |
527 | // place (at the very start of the editable). #10119 | 738 | // place (at the very start of the editable). http://dev.ckeditor.com/ticket/10119 |
528 | if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) { | 739 | if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) { |
529 | var rng = editor.createRange(); | 740 | var rng = editor.createRange(); |
530 | rng.moveToElementEditStart( editable ); | 741 | rng.moveToElementEditStart( editable ); |
@@ -539,9 +750,17 @@ | |||
539 | // On Webkit we use DOMFocusIn which is fired more often than focus - e.g. when moving from main editable | 750 | // On Webkit we use DOMFocusIn which is fired more often than focus - e.g. when moving from main editable |
540 | // to nested editable (or the opposite). Unlock selection all, but restore only when it was locked | 751 | // to nested editable (or the opposite). Unlock selection all, but restore only when it was locked |
541 | // for the same active element, what will e.g. mean restoring after displaying dialog. | 752 | // for the same active element, what will e.g. mean restoring after displaying dialog. |
542 | if ( restoreSel && CKEDITOR.env.webkit ) | 753 | if ( restoreSel && CKEDITOR.env.webkit ) { |
543 | restoreSel = editor._.previousActive && editor._.previousActive.equals( doc.getActive() ); | 754 | restoreSel = editor._.previousActive && editor._.previousActive.equals( doc.getActive() ); |
544 | 755 | ||
756 | // On Webkit when editor uses divarea, native focus causes editable viewport to scroll | ||
757 | // to the top (when there is no active selection inside while focusing) so the scroll | ||
758 | // position should be restored after focusing back editable area. (http://dev.ckeditor.com/ticket/14659) | ||
759 | if ( restoreSel && editor._.previousScrollTop != null && editor._.previousScrollTop != editable.$.scrollTop ) { | ||
760 | editable.$.scrollTop = editor._.previousScrollTop; | ||
761 | } | ||
762 | } | ||
763 | |||
545 | editor.unlockSelection( restoreSel ); | 764 | editor.unlockSelection( restoreSel ); |
546 | restoreSel = 0; | 765 | restoreSel = 0; |
547 | }, null, null, -1 ); | 766 | }, null, null, -1 ); |
@@ -588,7 +807,7 @@ | |||
588 | editable.attachListener( editable, 'mousedown', function( evt ) { | 807 | editable.attachListener( editable, 'mousedown', function( evt ) { |
589 | // IE scrolls document to top on right mousedown | 808 | // IE scrolls document to top on right mousedown |
590 | // when editor has no focus, remember this scroll | 809 | // when editor has no focus, remember this scroll |
591 | // position and revert it before context menu opens. (#5778) | 810 | // position and revert it before context menu opens. (http://dev.ckeditor.com/ticket/5778) |
592 | if ( evt.data.$.button == 2 ) { | 811 | if ( evt.data.$.button == 2 ) { |
593 | var sel = editor.document.getSelection(); | 812 | var sel = editor.document.getSelection(); |
594 | if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE ) | 813 | if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE ) |
@@ -607,9 +826,12 @@ | |||
607 | 826 | ||
608 | // When content doc is in standards mode, IE doesn't focus the editor when | 827 | // When content doc is in standards mode, IE doesn't focus the editor when |
609 | // clicking at the region below body (on html element) content, we emulate | 828 | // clicking at the region below body (on html element) content, we emulate |
610 | // the normal behavior on old IEs. (#1659, #7932) | 829 | // the normal behavior on old IEs. (http://dev.ckeditor.com/ticket/1659, http://dev.ckeditor.com/ticket/7932) |
611 | if ( doc.$.compatMode != 'BackCompat' ) { | 830 | if ( doc.$.compatMode != 'BackCompat' ) { |
612 | if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { | 831 | if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { |
832 | var textRng, | ||
833 | startRng; | ||
834 | |||
613 | html.on( 'mousedown', function( evt ) { | 835 | html.on( 'mousedown', function( evt ) { |
614 | evt = evt.data; | 836 | evt = evt.data; |
615 | 837 | ||
@@ -641,7 +863,7 @@ | |||
641 | html.removeListener( 'mousemove', onHover ); | 863 | html.removeListener( 'mousemove', onHover ); |
642 | removeListeners(); | 864 | removeListeners(); |
643 | 865 | ||
644 | // Make it in effect on mouse up. (#9022) | 866 | // Make it in effect on mouse up. (http://dev.ckeditor.com/ticket/9022) |
645 | textRng.select(); | 867 | textRng.select(); |
646 | } | 868 | } |
647 | 869 | ||
@@ -652,11 +874,11 @@ | |||
652 | evt.$.y < html.$.clientHeight && | 874 | evt.$.y < html.$.clientHeight && |
653 | evt.$.x < html.$.clientWidth ) { | 875 | evt.$.x < html.$.clientWidth ) { |
654 | // Start to build the text range. | 876 | // Start to build the text range. |
655 | var textRng = body.$.createTextRange(); | 877 | textRng = body.$.createTextRange(); |
656 | moveRangeToPoint( textRng, evt.$.clientX, evt.$.clientY ); | 878 | moveRangeToPoint( textRng, evt.$.clientX, evt.$.clientY ); |
657 | 879 | ||
658 | // Records the dragging start of the above text range. | 880 | // Records the dragging start of the above text range. |
659 | var startRng = textRng.duplicate(); | 881 | startRng = textRng.duplicate(); |
660 | 882 | ||
661 | html.on( 'mousemove', onHover ); | 883 | html.on( 'mousemove', onHover ); |
662 | outerDoc.on( 'mouseup', onSelectEnd ); | 884 | outerDoc.on( 'mouseup', onSelectEnd ); |
@@ -670,7 +892,7 @@ | |||
670 | if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) { | 892 | if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) { |
671 | html.on( 'mousedown', function( evt ) { | 893 | html.on( 'mousedown', function( evt ) { |
672 | if ( evt.data.getTarget().is( 'html' ) ) { | 894 | if ( evt.data.getTarget().is( 'html' ) ) { |
673 | // Limit the text selection mouse move inside of editable. (#9715) | 895 | // Limit the text selection mouse move inside of editable. (http://dev.ckeditor.com/ticket/9715) |
674 | outerDoc.on( 'mouseup', onSelectEnd ); | 896 | outerDoc.on( 'mouseup', onSelectEnd ); |
675 | html.on( 'mouseup', onSelectEnd ); | 897 | html.on( 'mouseup', onSelectEnd ); |
676 | } | 898 | } |
@@ -684,6 +906,14 @@ | |||
684 | // 2. After the accomplish of keyboard and mouse events. | 906 | // 2. After the accomplish of keyboard and mouse events. |
685 | editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor ); | 907 | editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor ); |
686 | editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor ); | 908 | editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor ); |
909 | // http://dev.ckeditor.com/ticket/14407 - Don't even let anything happen if the selection is in a non-editable element. | ||
910 | editable.attachListener( editable, 'keydown', function( evt ) { | ||
911 | var sel = this.getSelection( 1 ); | ||
912 | if ( nonEditableAscendant( sel ) ) { | ||
913 | sel.selectElement( nonEditableAscendant( sel ) ); | ||
914 | evt.data.preventDefault(); | ||
915 | } | ||
916 | }, editor ); | ||
687 | // Always fire the selection change on focus gain. | 917 | // Always fire the selection change on focus gain. |
688 | // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and | 918 | // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and |
689 | // we need synchronization between those listeners to not lost cached editor._.previousActive property | 919 | // we need synchronization between those listeners to not lost cached editor._.previousActive property |
@@ -693,7 +923,7 @@ | |||
693 | editor.selectionChange( 1 ); | 923 | editor.selectionChange( 1 ); |
694 | } ); | 924 | } ); |
695 | 925 | ||
696 | // #9699: On Webkit&Gecko in inline editor we have to check selection when it was changed | 926 | // http://dev.ckeditor.com/ticket/9699: On Webkit&Gecko in inline editor we have to check selection when it was changed |
697 | // by dragging and releasing mouse button outside editable. Dragging (mousedown) | 927 | // by dragging and releasing mouse button outside editable. Dragging (mousedown) |
698 | // has to be initialized in editable, but for mouseup we listen on document element. | 928 | // has to be initialized in editable, but for mouseup we listen on document element. |
699 | if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) { | 929 | if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) { |
@@ -707,11 +937,11 @@ | |||
707 | mouseDown = 0; | 937 | mouseDown = 0; |
708 | } ); | 938 | } ); |
709 | } | 939 | } |
710 | // In all other cases listen on simple mouseup over editable, as we did before #9699. | 940 | // In all other cases listen on simple mouseup over editable, as we did before http://dev.ckeditor.com/ticket/9699. |
711 | // | 941 | // |
712 | // Use document instead of editable in non-IEs for observing mouseup | 942 | // Use document instead of editable in non-IEs for observing mouseup |
713 | // since editable won't fire the event if selection process started within iframe and ended out | 943 | // since editable won't fire the event if selection process started within iframe and ended out |
714 | // of the editor (#9851). | 944 | // of the editor (http://dev.ckeditor.com/ticket/9851). |
715 | else { | 945 | else { |
716 | editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor ); | 946 | editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor ); |
717 | } | 947 | } |
@@ -733,18 +963,18 @@ | |||
733 | case 8: // BACKSPACE | 963 | case 8: // BACKSPACE |
734 | case 45: // INS | 964 | case 45: // INS |
735 | case 46: // DEl | 965 | case 46: // DEl |
736 | removeFillingCharSequenceNode( editable ); | 966 | if ( editable.hasFocus ) { |
967 | removeFillingCharSequenceNode( editable ); | ||
968 | } | ||
737 | } | 969 | } |
738 | 970 | ||
739 | }, null, null, -1 ); | 971 | }, null, null, -1 ); |
740 | } | 972 | } |
741 | 973 | ||
742 | // Automatically select non-editable element when navigating into | ||
743 | // it by left/right or backspace/del keys. | ||
744 | editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 ); | 974 | editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 ); |
745 | 975 | ||
746 | function moveRangeToPoint( range, x, y ) { | 976 | function moveRangeToPoint( range, x, y ) { |
747 | // Error prune in IE7. (#9034, #9110) | 977 | // Error prune in IE7. (http://dev.ckeditor.com/ticket/9034, http://dev.ckeditor.com/ticket/9110) |
748 | try { | 978 | try { |
749 | range.moveToPoint( x, y ); | 979 | range.moveToPoint( x, y ); |
750 | } catch ( e ) {} | 980 | } catch ( e ) {} |
@@ -765,14 +995,27 @@ | |||
765 | range = sel.createRange(); | 995 | range = sel.createRange(); |
766 | 996 | ||
767 | // The selection range is reported on host, but actually it should applies to the content doc. | 997 | // The selection range is reported on host, but actually it should applies to the content doc. |
768 | if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ ) | 998 | // The parentElement may be null for read only mode in IE10 and below (http://dev.ckeditor.com/ticket/9780). |
999 | if ( sel.type != 'None' && range.parentElement() && range.parentElement().ownerDocument == doc.$ ) | ||
769 | range.select(); | 1000 | range.select(); |
770 | } | 1001 | } |
1002 | |||
1003 | function nonEditableAscendant( sel ) { | ||
1004 | if ( CKEDITOR.env.ie ) { | ||
1005 | var range = sel.getRanges()[ 0 ], | ||
1006 | ascendant = range ? range.startContainer.getAscendant( function( parent ) { | ||
1007 | return parent.type == CKEDITOR.NODE_ELEMENT && | ||
1008 | ( parent.getAttribute( 'contenteditable' ) == 'false' || parent.getAttribute( 'contenteditable' ) == 'true' ); | ||
1009 | }, true ) : null ; | ||
1010 | |||
1011 | return range && ascendant.getAttribute( 'contenteditable' ) == 'false' && ascendant; | ||
1012 | } | ||
1013 | } | ||
771 | } ); | 1014 | } ); |
772 | 1015 | ||
773 | editor.on( 'setData', function() { | 1016 | editor.on( 'setData', function() { |
774 | // Invalidate locked selection when unloading DOM. | 1017 | // Invalidate locked selection when unloading DOM. |
775 | // (#9521, #5217#comment:32 and #11500#comment:11) | 1018 | // (http://dev.ckeditor.com/ticket/9521, http://dev.ckeditor.com/ticket/5217#comment:32 and http://dev.ckeditor.com/ticket/11500#comment:11) |
776 | editor.unlockSelection(); | 1019 | editor.unlockSelection(); |
777 | 1020 | ||
778 | // Webkit's selection will mess up after the data loading. | 1021 | // Webkit's selection will mess up after the data loading. |
@@ -786,7 +1029,7 @@ | |||
786 | editor.unlockSelection(); | 1029 | editor.unlockSelection(); |
787 | } ); | 1030 | } ); |
788 | 1031 | ||
789 | // IE9 might cease to work if there's an object selection inside the iframe (#7639). | 1032 | // IE9 might cease to work if there's an object selection inside the iframe (http://dev.ckeditor.com/ticket/7639). |
790 | if ( CKEDITOR.env.ie9Compat ) | 1033 | if ( CKEDITOR.env.ie9Compat ) |
791 | editor.on( 'beforeDestroy', clearSelection, null, null, 9 ); | 1034 | editor.on( 'beforeDestroy', clearSelection, null, null, 9 ); |
792 | 1035 | ||
@@ -802,7 +1045,7 @@ | |||
802 | // When loaded data are ready check whether hidden selection container was not loaded. | 1045 | // When loaded data are ready check whether hidden selection container was not loaded. |
803 | editor.on( 'loadSnapshot', function() { | 1046 | editor.on( 'loadSnapshot', function() { |
804 | var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ), | 1047 | var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ), |
805 | // TODO replace with el.find() which will be introduced in #9764, | 1048 | // TODO replace with el.find() which will be introduced in http://dev.ckeditor.com/ticket/9764, |
806 | // because it may happen that hidden sel container won't be the last element. | 1049 | // because it may happen that hidden sel container won't be the last element. |
807 | last = editor.editable().getLast( isElement ); | 1050 | last = editor.editable().getLast( isElement ); |
808 | 1051 | ||
@@ -849,7 +1092,7 @@ | |||
849 | } ); | 1092 | } ); |
850 | 1093 | ||
851 | // On WebKit only, we need a special "filling" char on some situations | 1094 | // On WebKit only, we need a special "filling" char on some situations |
852 | // (#1272). Here we set the events that should invalidate that char. | 1095 | // (http://dev.ckeditor.com/ticket/1272). Here we set the events that should invalidate that char. |
853 | if ( CKEDITOR.env.webkit ) { | 1096 | if ( CKEDITOR.env.webkit ) { |
854 | CKEDITOR.on( 'instanceReady', function( evt ) { | 1097 | CKEDITOR.on( 'instanceReady', function( evt ) { |
855 | var editor = evt.editor; | 1098 | var editor = evt.editor; |
@@ -864,7 +1107,7 @@ | |||
864 | 1107 | ||
865 | // Filter Undo snapshot's HTML to get rid of Filling Char Sequence. | 1108 | // Filter Undo snapshot's HTML to get rid of Filling Char Sequence. |
866 | // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's | 1109 | // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's |
867 | // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (#13816). | 1110 | // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (http://dev.ckeditor.com/ticket/13816). |
868 | editor.on( 'getSnapshot', function( evt ) { | 1111 | editor.on( 'getSnapshot', function( evt ) { |
869 | if ( evt.data ) { | 1112 | if ( evt.data ) { |
870 | evt.data = removeFillingCharSequenceString( evt.data ); | 1113 | evt.data = removeFillingCharSequenceString( evt.data ); |
@@ -873,7 +1116,7 @@ | |||
873 | 1116 | ||
874 | // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat | 1117 | // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat |
875 | // instead of #getData because once removed, FCSeq may leave an empty element, | 1118 | // instead of #getData because once removed, FCSeq may leave an empty element, |
876 | // which should be pruned by the dataProcessor (#13816). | 1119 | // which should be pruned by the dataProcessor (http://dev.ckeditor.com/ticket/13816). |
877 | // Note: Used low priority to filter when dataProcessor works on strings, | 1120 | // Note: Used low priority to filter when dataProcessor works on strings, |
878 | // not pseudo–DOM. | 1121 | // not pseudo–DOM. |
879 | editor.on( 'toDataFormat', function( evt ) { | 1122 | editor.on( 'toDataFormat', function( evt ) { |
@@ -1039,9 +1282,6 @@ | |||
1039 | */ | 1282 | */ |
1040 | CKEDITOR.SELECTION_ELEMENT = 3; | 1283 | CKEDITOR.SELECTION_ELEMENT = 3; |
1041 | 1284 | ||
1042 | var isMSSelection = typeof window.getSelection != 'function', | ||
1043 | nextRev = 1; | ||
1044 | |||
1045 | /** | 1285 | /** |
1046 | * Manipulates the selection within a DOM element. If the current browser selection | 1286 | * Manipulates the selection within a DOM element. If the current browser selection |
1047 | * spans outside of the element, an empty selection object is returned. | 1287 | * spans outside of the element, an empty selection object is returned. |
@@ -1121,7 +1361,7 @@ | |||
1121 | 1361 | ||
1122 | // Selection out of concerned range, empty the selection. | 1362 | // Selection out of concerned range, empty the selection. |
1123 | // TODO check whether this condition cannot be reverted to its old | 1363 | // TODO check whether this condition cannot be reverted to its old |
1124 | // form (commented out) after we closed #10438. | 1364 | // form (commented out) after we closed http://dev.ckeditor.com/ticket/10438. |
1125 | //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) { | 1365 | //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) { |
1126 | if ( !( | 1366 | if ( !( |
1127 | rangeParent && | 1367 | rangeParent && |
@@ -1142,10 +1382,6 @@ | |||
1142 | var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1, | 1382 | var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1, |
1143 | a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 }; | 1383 | a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 }; |
1144 | 1384 | ||
1145 | // #13816 | ||
1146 | var fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ), | ||
1147 | fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ); | ||
1148 | |||
1149 | CKEDITOR.tools.extend( CKEDITOR.dom.selection, { | 1385 | CKEDITOR.tools.extend( CKEDITOR.dom.selection, { |
1150 | _removeFillingCharSequenceString: removeFillingCharSequenceString, | 1386 | _removeFillingCharSequenceString: removeFillingCharSequenceString, |
1151 | _createFillingCharSequenceNode: createFillingCharSequenceNode, | 1387 | _createFillingCharSequenceNode: createFillingCharSequenceNode, |
@@ -1167,7 +1403,7 @@ | |||
1167 | * | 1403 | * |
1168 | * var selection = editor.getSelection().getNative(); | 1404 | * var selection = editor.getSelection().getNative(); |
1169 | * | 1405 | * |
1170 | * @returns {Object} The native browser selection object. | 1406 | * @returns {Object} The native browser selection object or null if this is a fake selection. |
1171 | */ | 1407 | */ |
1172 | getNative: function() { | 1408 | getNative: function() { |
1173 | if ( this._.cache.nativeSel !== undefined ) | 1409 | if ( this._.cache.nativeSel !== undefined ) |
@@ -1259,7 +1495,7 @@ | |||
1259 | * alert( ranges.length ); | 1495 | * alert( ranges.length ); |
1260 | * | 1496 | * |
1261 | * @method | 1497 | * @method |
1262 | * @param {Boolean} [onlyEditables] If set to `true`, this function retrives editable ranges only. | 1498 | * @param {Boolean} [onlyEditables] If set to `true`, this function retrieves editable ranges only. |
1263 | * @returns {Array} Range instances that represent the current selection. | 1499 | * @returns {Array} Range instances that represent the current selection. |
1264 | */ | 1500 | */ |
1265 | getRanges: ( function() { | 1501 | getRanges: ( function() { |
@@ -1290,7 +1526,7 @@ | |||
1290 | index = -1, | 1526 | index = -1, |
1291 | position, distance, container; | 1527 | position, distance, container; |
1292 | 1528 | ||
1293 | // Binary search over all element childs to test the range to see whether | 1529 | // Binary search over all element children to test the range to see whether |
1294 | // range is right on the boundary of one element. | 1530 | // range is right on the boundary of one element. |
1295 | while ( startIndex <= endIndex ) { | 1531 | while ( startIndex <= endIndex ) { |
1296 | index = Math.floor( ( startIndex + endIndex ) / 2 ); | 1532 | index = Math.floor( ( startIndex + endIndex ) / 2 ); |
@@ -1306,8 +1542,8 @@ | |||
1306 | return { container: parent, offset: getNodeIndex( child ) }; | 1542 | return { container: parent, offset: getNodeIndex( child ) }; |
1307 | } | 1543 | } |
1308 | 1544 | ||
1309 | // All childs are text nodes, | 1545 | // All children are text nodes, |
1310 | // or to the right hand of test range are all text nodes. (#6992) | 1546 | // or to the right hand of test range are all text nodes. (http://dev.ckeditor.com/ticket/6992) |
1311 | if ( index == -1 || index == siblings.length - 1 && position < 0 ) { | 1547 | if ( index == -1 || index == siblings.length - 1 && position < 0 ) { |
1312 | // Adapt test range to embrace the entire parent contents. | 1548 | // Adapt test range to embrace the entire parent contents. |
1313 | testRange.moveToElementText( parent ); | 1549 | testRange.moveToElementText( parent ); |
@@ -1315,7 +1551,7 @@ | |||
1315 | 1551 | ||
1316 | // IE report line break as CRLF with range.text but | 1552 | // IE report line break as CRLF with range.text but |
1317 | // only LF with textnode.nodeValue, normalize them to avoid | 1553 | // only LF with textnode.nodeValue, normalize them to avoid |
1318 | // breaking character counting logic below. (#3949) | 1554 | // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949) |
1319 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; | 1555 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; |
1320 | 1556 | ||
1321 | siblings = parent.childNodes; | 1557 | siblings = parent.childNodes; |
@@ -1351,7 +1587,7 @@ | |||
1351 | 1587 | ||
1352 | // IE report line break as CRLF with range.text but | 1588 | // IE report line break as CRLF with range.text but |
1353 | // only LF with textnode.nodeValue, normalize them to avoid | 1589 | // only LF with textnode.nodeValue, normalize them to avoid |
1354 | // breaking character counting logic below. (#3949) | 1590 | // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949) |
1355 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; | 1591 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; |
1356 | 1592 | ||
1357 | // Actual range anchor right beside test range at the inner boundary of text node. | 1593 | // Actual range anchor right beside test range at the inner boundary of text node. |
@@ -1368,7 +1604,7 @@ | |||
1368 | } | 1604 | } |
1369 | child = sibling; | 1605 | child = sibling; |
1370 | } | 1606 | } |
1371 | // Measurement in IE could be somtimes wrong because of <select> element. (#4611) | 1607 | // Measurement in IE could be sometimes wrong because of <select> element. (http://dev.ckeditor.com/ticket/4611) |
1372 | catch ( e ) { | 1608 | catch ( e ) { |
1373 | return { container: parent, offset: getNodeIndex( child ) }; | 1609 | return { container: parent, offset: getNodeIndex( child ) }; |
1374 | } | 1610 | } |
@@ -1400,7 +1636,7 @@ | |||
1400 | boundaryInfo = getBoundaryInformation( nativeRange ); | 1636 | boundaryInfo = getBoundaryInformation( nativeRange ); |
1401 | range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); | 1637 | range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); |
1402 | 1638 | ||
1403 | // Correct an invalid IE range case on empty list item. (#5850) | 1639 | // Correct an invalid IE range case on empty list item. (http://dev.ckeditor.com/ticket/5850) |
1404 | if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() ) | 1640 | if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() ) |
1405 | range.collapse(); | 1641 | range.collapse(); |
1406 | 1642 | ||
@@ -1432,7 +1668,7 @@ | |||
1432 | } )() : | 1668 | } )() : |
1433 | function() { | 1669 | function() { |
1434 | // On browsers implementing the W3C range, we simply | 1670 | // On browsers implementing the W3C range, we simply |
1435 | // tranform the native ranges in CKEDITOR.dom.range | 1671 | // transform the native ranges in CKEDITOR.dom.range |
1436 | // instances. | 1672 | // instances. |
1437 | 1673 | ||
1438 | var ranges = [], | 1674 | var ranges = [], |
@@ -1465,7 +1701,7 @@ | |||
1465 | return ranges; | 1701 | return ranges; |
1466 | 1702 | ||
1467 | // Split range into multiple by read-only nodes. | 1703 | // Split range into multiple by read-only nodes. |
1468 | // Clone ranges array to avoid changing cached ranges (#11493). | 1704 | // Clone ranges array to avoid changing cached ranges (http://dev.ckeditor.com/ticket/11493). |
1469 | return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) ); | 1705 | return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) ); |
1470 | }; | 1706 | }; |
1471 | } )(), | 1707 | } )(), |
@@ -1499,11 +1735,11 @@ | |||
1499 | 1735 | ||
1500 | // Decrease the range content to exclude particial | 1736 | // Decrease the range content to exclude particial |
1501 | // selected node on the start which doesn't have | 1737 | // selected node on the start which doesn't have |
1502 | // visual impact. ( #3231 ) | 1738 | // visual impact. ( http://dev.ckeditor.com/ticket/3231 ) |
1503 | while ( 1 ) { | 1739 | while ( 1 ) { |
1504 | var startContainer = range.startContainer, | 1740 | var startContainer = range.startContainer, |
1505 | startOffset = range.startOffset; | 1741 | startOffset = range.startOffset; |
1506 | // Limit the fix only to non-block elements.(#3950) | 1742 | // Limit the fix only to non-block elements.(http://dev.ckeditor.com/ticket/3950) |
1507 | if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() ) | 1743 | if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() ) |
1508 | range.setStartAfter( startContainer ); | 1744 | range.setStartAfter( startContainer ); |
1509 | else | 1745 | else |
@@ -1545,7 +1781,7 @@ | |||
1545 | * var element = editor.getSelection().getSelectedElement(); | 1781 | * var element = editor.getSelection().getSelectedElement(); |
1546 | * alert( element.getName() ); | 1782 | * alert( element.getName() ); |
1547 | * | 1783 | * |
1548 | * @returns {CKEDITOR.dom.element} The selected element. Null if no | 1784 | * @returns {CKEDITOR.dom.element/null} The selected element. `null` if no |
1549 | * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}. | 1785 | * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}. |
1550 | */ | 1786 | */ |
1551 | getSelectedElement: function() { | 1787 | getSelectedElement: function() { |
@@ -1628,7 +1864,7 @@ | |||
1628 | 1864 | ||
1629 | if ( restore ) { | 1865 | if ( restore ) { |
1630 | var selectedElement = this.getSelectedElement(), | 1866 | var selectedElement = this.getSelectedElement(), |
1631 | ranges = !selectedElement && this.getRanges(), | 1867 | ranges = this.getRanges(), |
1632 | faked = this.isFake; | 1868 | faked = this.isFake; |
1633 | } | 1869 | } |
1634 | 1870 | ||
@@ -1642,7 +1878,10 @@ | |||
1642 | if ( !( common && common.getAscendant( 'body', 1 ) ) ) | 1878 | if ( !( common && common.getAscendant( 'body', 1 ) ) ) |
1643 | return; | 1879 | return; |
1644 | 1880 | ||
1645 | if ( faked ) | 1881 | if ( isTableSelection( ranges ) ) { |
1882 | // Tables have it's own selection method. | ||
1883 | performFakeTableSelection.call( this, ranges ); | ||
1884 | } else if ( faked ) | ||
1646 | this.fake( selectedElement ); | 1885 | this.fake( selectedElement ); |
1647 | else if ( selectedElement ) | 1886 | else if ( selectedElement ) |
1648 | this.selectElement( selectedElement ); | 1887 | this.selectElement( selectedElement ); |
@@ -1714,7 +1953,7 @@ | |||
1714 | // Check if there's a hiddenSelectionContainer in editable at some index. | 1953 | // Check if there's a hiddenSelectionContainer in editable at some index. |
1715 | // Some ranges may be anchored after the hiddenSelectionContainer and, | 1954 | // Some ranges may be anchored after the hiddenSelectionContainer and, |
1716 | // once the container is removed while resetting the selection, they | 1955 | // once the container is removed while resetting the selection, they |
1717 | // may need new endOffset (one element less within the range) (#11021 #11393). | 1956 | // may need new endOffset (one element less within the range) (http://dev.ckeditor.com/ticket/11021 http://dev.ckeditor.com/ticket/11393). |
1718 | if ( hadHiddenSelectionContainer ) | 1957 | if ( hadHiddenSelectionContainer ) |
1719 | fixRangesAfterHiddenSelectionContainer( ranges, this.root ); | 1958 | fixRangesAfterHiddenSelectionContainer( ranges, this.root ); |
1720 | 1959 | ||
@@ -1742,6 +1981,15 @@ | |||
1742 | return; | 1981 | return; |
1743 | } | 1982 | } |
1744 | 1983 | ||
1984 | // Handle special case - fake selection of table cells. | ||
1985 | if ( editor && editor.plugins.tableselection && | ||
1986 | CKEDITOR.plugins.tableselection.isSupportedEnvironment && | ||
1987 | isTableSelection( ranges ) && !isSelectingTable | ||
1988 | ) { | ||
1989 | performFakeTableSelection.call( this, ranges ); | ||
1990 | return; | ||
1991 | } | ||
1992 | |||
1745 | if ( isMSSelection ) { | 1993 | if ( isMSSelection ) { |
1746 | var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), | 1994 | var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), |
1747 | fillerTextRegex = /\ufeff|\u00a0/, | 1995 | fillerTextRegex = /\ufeff|\u00a0/, |
@@ -1775,7 +2023,7 @@ | |||
1775 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells || | 2023 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells || |
1776 | range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) { | 2024 | range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) { |
1777 | range.shrink( CKEDITOR.NODE_ELEMENT, true ); | 2025 | range.shrink( CKEDITOR.NODE_ELEMENT, true ); |
1778 | // The range might get collapsed (#7975). Update cached variable. | 2026 | // The range might get collapsed (http://dev.ckeditor.com/ticket/7975). Update cached variable. |
1779 | collapsed = range.collapsed; | 2027 | collapsed = range.collapsed; |
1780 | } | 2028 | } |
1781 | 2029 | ||
@@ -1817,18 +2065,18 @@ | |||
1817 | 2065 | ||
1818 | // Append a temporary <span></span> before the selection. | 2066 | // Append a temporary <span></span> before the selection. |
1819 | // This is needed to avoid IE destroying selections inside empty | 2067 | // This is needed to avoid IE destroying selections inside empty |
1820 | // inline elements, like <b></b> (#253). | 2068 | // inline elements, like <b></b> (http://dev.ckeditor.com/ticket/253). |
1821 | // It is also needed when placing the selection right after an inline | 2069 | // It is also needed when placing the selection right after an inline |
1822 | // element to avoid the selection moving inside of it. | 2070 | // element to avoid the selection moving inside of it. |
1823 | dummySpan = range.document.createElement( 'span' ); | 2071 | dummySpan = range.document.createElement( 'span' ); |
1824 | dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359. | 2072 | dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See http://dev.ckeditor.com/ticket/1359. |
1825 | dummySpan.insertBefore( startNode ); | 2073 | dummySpan.insertBefore( startNode ); |
1826 | 2074 | ||
1827 | if ( isStartMarkerAlone ) { | 2075 | if ( isStartMarkerAlone ) { |
1828 | // To expand empty blocks or line spaces after <br>, we need | 2076 | // To expand empty blocks or line spaces after <br>, we need |
1829 | // instead to have any char, which will be later deleted using the | 2077 | // instead to have any char, which will be later deleted using the |
1830 | // selection. | 2078 | // selection. |
1831 | // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) | 2079 | // \ufeff = Zero Width No-Break Space (U+FEFF). (http://dev.ckeditor.com/ticket/1359) |
1832 | range.document.createText( '\ufeff' ).insertBefore( startNode ); | 2080 | range.document.createText( '\ufeff' ).insertBefore( startNode ); |
1833 | } | 2081 | } |
1834 | } | 2082 | } |
@@ -1860,7 +2108,7 @@ | |||
1860 | } else { | 2108 | } else { |
1861 | var sel = this.getNative(); | 2109 | var sel = this.getNative(); |
1862 | 2110 | ||
1863 | // getNative() returns null if iframe is "display:none" in FF. (#6577) | 2111 | // getNative() returns null if iframe is "display:none" in FF. (http://dev.ckeditor.com/ticket/6577) |
1864 | if ( !sel ) | 2112 | if ( !sel ) |
1865 | return; | 2113 | return; |
1866 | 2114 | ||
@@ -1876,7 +2124,7 @@ | |||
1876 | between.setStart( left.endContainer, left.endOffset ); | 2124 | between.setStart( left.endContainer, left.endOffset ); |
1877 | between.setEnd( right.startContainer, right.startOffset ); | 2125 | between.setEnd( right.startContainer, right.startOffset ); |
1878 | 2126 | ||
1879 | // Don't confused by Firefox adjancent multi-ranges | 2127 | // Don't confused by Firefox adjacent multi-ranges |
1880 | // introduced by table cells selection. | 2128 | // introduced by table cells selection. |
1881 | if ( !between.collapsed ) { | 2129 | if ( !between.collapsed ) { |
1882 | between.shrink( CKEDITOR.NODE_ELEMENT, true ); | 2130 | between.shrink( CKEDITOR.NODE_ELEMENT, true ); |
@@ -1885,7 +2133,7 @@ | |||
1885 | 2133 | ||
1886 | // The following cases has to be considered: | 2134 | // The following cases has to be considered: |
1887 | // 1. <span contenteditable="false">[placeholder]</span> | 2135 | // 1. <span contenteditable="false">[placeholder]</span> |
1888 | // 2. <input contenteditable="false" type="radio"/> (#6621) | 2136 | // 2. <input contenteditable="false" type="radio"/> (http://dev.ckeditor.com/ticket/6621) |
1889 | if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) { | 2137 | if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) { |
1890 | right.setStart( left.startContainer, left.startOffset ); | 2138 | right.setStart( left.startContainer, left.startOffset ); |
1891 | ranges.splice( i--, 1 ); | 2139 | ranges.splice( i--, 1 ); |
@@ -1900,7 +2148,7 @@ | |||
1900 | 2148 | ||
1901 | if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { | 2149 | if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { |
1902 | // Append a zero-width space so WebKit will not try to | 2150 | // Append a zero-width space so WebKit will not try to |
1903 | // move the selection by itself (#1272). | 2151 | // move the selection by itself (http://dev.ckeditor.com/ticket/1272). |
1904 | var fillingChar = createFillingCharSequenceNode( this.root ); | 2152 | var fillingChar = createFillingCharSequenceNode( this.root ); |
1905 | range.insertNode( fillingChar ); | 2153 | range.insertNode( fillingChar ); |
1906 | 2154 | ||
@@ -1954,14 +2202,20 @@ | |||
1954 | * displayed to the user. | 2202 | * displayed to the user. |
1955 | * | 2203 | * |
1956 | * @param {CKEDITOR.dom.element} element The element to be "selected". | 2204 | * @param {CKEDITOR.dom.element} element The element to be "selected". |
2205 | * @param {String} [ariaLabel] A string to be used by the screen reader to describe the selection. | ||
1957 | */ | 2206 | */ |
1958 | fake: function( element ) { | 2207 | fake: function( element, ariaLabel ) { |
1959 | var editor = this.root.editor; | 2208 | var editor = this.root.editor; |
1960 | 2209 | ||
2210 | // Attempt to retrieve aria-label if possible (http://dev.ckeditor.com/ticket/14539). | ||
2211 | if ( ariaLabel === undefined && element.hasAttribute( 'aria-label' ) ) { | ||
2212 | ariaLabel = element.getAttribute( 'aria-label' ); | ||
2213 | } | ||
2214 | |||
1961 | // Cleanup after previous selection - e.g. remove hidden sel container. | 2215 | // Cleanup after previous selection - e.g. remove hidden sel container. |
1962 | this.reset(); | 2216 | this.reset(); |
1963 | 2217 | ||
1964 | hideSelection( editor ); | 2218 | hideSelection( editor, ariaLabel ); |
1965 | 2219 | ||
1966 | // Set this value after executing hiseSelection, because it may | 2220 | // Set this value after executing hiseSelection, because it may |
1967 | // cause reset() which overwrites cache. | 2221 | // cause reset() which overwrites cache. |
@@ -2012,6 +2266,38 @@ | |||
2012 | }, | 2266 | }, |
2013 | 2267 | ||
2014 | /** | 2268 | /** |
2269 | * Checks if the selection contains an HTML element inside a table. | ||
2270 | * Returns `false` for text selection inside a table (e.g. it will return `false` | ||
2271 | * for text selected in one cell). | ||
2272 | * | ||
2273 | * editor.getSelection().isInTable(); | ||
2274 | * | ||
2275 | * @since 4.7.0 | ||
2276 | * @param {Boolean} [allowPartialSelection=false] Whether a partial cell selection should be included. | ||
2277 | * Added in 4.7.2. | ||
2278 | * @returns {Boolean} | ||
2279 | */ | ||
2280 | isInTable: function( allowPartialSelection ) { | ||
2281 | return isTableSelection( this.getRanges(), allowPartialSelection ); | ||
2282 | }, | ||
2283 | |||
2284 | /** | ||
2285 | * Checks if the selection contains only one range which is collapsed. | ||
2286 | * | ||
2287 | * if ( editor.getSelection().isCollapsed() ) { | ||
2288 | * // Do something when the selection is collapsed. | ||
2289 | * } | ||
2290 | * | ||
2291 | * @since 4.7.3 | ||
2292 | * @returns {Boolean} | ||
2293 | */ | ||
2294 | isCollapsed: function() { | ||
2295 | var ranges = this.getRanges(); | ||
2296 | |||
2297 | return ranges.length === 1 && ranges[ 0 ].collapsed; | ||
2298 | }, | ||
2299 | |||
2300 | /** | ||
2015 | * Creates a bookmark for each range of this selection (from {@link #getRanges}) | 2301 | * Creates a bookmark for each range of this selection (from {@link #getRanges}) |
2016 | * by calling the {@link CKEDITOR.dom.range#createBookmark} method, | 2302 | * by calling the {@link CKEDITOR.dom.range#createBookmark} method, |
2017 | * with extra care taken to avoid interference among those ranges. The arguments | 2303 | * with extra care taken to avoid interference among those ranges. The arguments |
@@ -2064,17 +2350,19 @@ | |||
2064 | 2350 | ||
2065 | // It may happen that the content change during loading, before selection is set so bookmark leads to text node. | 2351 | // It may happen that the content change during loading, before selection is set so bookmark leads to text node. |
2066 | if ( bookmarks.isFake ) { | 2352 | if ( bookmarks.isFake ) { |
2067 | node = ranges[ 0 ].getEnclosedNode(); | 2353 | node = isTableSelection( ranges ) ? ranges[ 0 ]._getTableElement() : ranges[ 0 ].getEnclosedNode(); |
2354 | |||
2068 | if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) { | 2355 | if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) { |
2069 | CKEDITOR.warn( 'selection-not-fake' ); | 2356 | CKEDITOR.warn( 'selection-not-fake' ); |
2070 | bookmarks.isFake = 0; | 2357 | bookmarks.isFake = 0; |
2071 | } | 2358 | } |
2072 | } | 2359 | } |
2073 | 2360 | ||
2074 | if ( bookmarks.isFake ) | 2361 | if ( bookmarks.isFake && !isTableSelection( ranges ) ) { |
2075 | this.fake( node ); | 2362 | this.fake( node ); |
2076 | else | 2363 | } else { |
2077 | this.selectRanges( ranges ); | 2364 | this.selectRanges( ranges ); |
2365 | } | ||
2078 | 2366 | ||
2079 | return this; | 2367 | return this; |
2080 | }, | 2368 | }, |
@@ -2111,7 +2399,7 @@ | |||
2111 | * Remove all the selection ranges from the document. | 2399 | * Remove all the selection ranges from the document. |
2112 | */ | 2400 | */ |
2113 | removeAllRanges: function() { | 2401 | removeAllRanges: function() { |
2114 | // Don't clear selection outside this selection's root (#11500). | 2402 | // Don't clear selection outside this selection's root (http://dev.ckeditor.com/ticket/11500). |
2115 | if ( this.getType() == CKEDITOR.SELECTION_NONE ) | 2403 | if ( this.getType() == CKEDITOR.SELECTION_NONE ) |
2116 | return; | 2404 | return; |
2117 | 2405 | ||