aboutsummaryrefslogtreecommitdiff
path: root/sources/core/selection.js
diff options
context:
space:
mode:
Diffstat (limited to 'sources/core/selection.js')
-rw-r--r--sources/core/selection.js182
1 files changed, 92 insertions, 90 deletions
diff --git a/sources/core/selection.js b/sources/core/selection.js
index fec3b7fc..573b890e 100644
--- a/sources/core/selection.js
+++ b/sources/core/selection.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2016, 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
@@ -138,72 +138,81 @@
138 return false; 138 return false;
139 } 139 }
140 140
141 function createFillingChar( element ) { 141 function createFillingCharSequenceNode( editable ) {
142 removeFillingChar( element, false ); 142 removeFillingCharSequenceNode( editable, false );
143 143
144 var fillingChar = element.getDocument().createText( '\u200B' ); 144 var fillingChar = editable.getDocument().createText( fillingCharSequence );
145 element.setCustomData( 'cke-fillingChar', fillingChar ); 145 editable.setCustomData( 'cke-fillingChar', fillingChar );
146 146
147 return fillingChar; 147 return fillingChar;
148 } 148 }
149 149
150 function getFillingChar( element ) {
151 return element.getCustomData( 'cke-fillingChar' );
152 }
153
154 // Checks if a filling char has been used, eventualy removing it (#1272). 150 // Checks if a filling char has been used, eventualy removing it (#1272).
155 function checkFillingChar( element ) { 151 function checkFillingCharSequenceNodeReady( editable ) {
156 var fillingChar = getFillingChar( element ); 152 var fillingChar = editable.getCustomData( 'cke-fillingChar' );
153
157 if ( fillingChar ) { 154 if ( fillingChar ) {
158 // Use this flag to avoid removing the filling char right after 155 // Use this flag to avoid removing the filling char right after
159 // creating it. 156 // creating it.
160 if ( fillingChar.getCustomData( 'ready' ) ) 157 if ( fillingChar.getCustomData( 'ready' ) ) {
161 removeFillingChar( element ); 158 removeFillingCharSequenceNode( editable );
162 else 159 } else {
163 fillingChar.setCustomData( 'ready', 1 ); 160 fillingChar.setCustomData( 'ready', 1 );
161 }
164 } 162 }
165 } 163 }
166 164
167 function removeFillingChar( element, keepSelection ) { 165 function removeFillingCharSequenceNode( editable, keepSelection ) {
168 var fillingChar = element && element.removeCustomData( 'cke-fillingChar' ); 166 var fillingChar = editable && editable.removeCustomData( 'cke-fillingChar' );
169 if ( fillingChar ) {
170 167
168 if ( fillingChar ) {
171 // Text selection position might get mangled by 169 // Text selection position might get mangled by
172 // subsequent dom modification, save it now for restoring. (#8617) 170 // subsequent dom modification, save it now for restoring. (#8617)
173 if ( keepSelection !== false ) { 171 if ( keepSelection !== false ) {
174 var bm, 172 var sel = editable.getDocument().getSelection().getNative(),
175 sel = element.getDocument().getSelection().getNative(),
176 // Be error proof. 173 // Be error proof.
177 range = sel && sel.type != 'None' && sel.getRangeAt( 0 ); 174 range = sel && sel.type != 'None' && sel.getRangeAt( 0 ),
175 fillingCharSeqLength = fillingCharSequence.length;
178 176
179 if ( fillingChar.getLength() > 1 && range && range.intersectsNode( fillingChar.$ ) ) { 177 // If there's some text other than the sequence in the FC text node and the range
180 bm = createNativeSelectionBookmark( sel ); 178 // intersects with that node...
179 if ( fillingChar.getLength() > fillingCharSeqLength && range && range.intersectsNode( fillingChar.$ ) ) {
180 var bm = createNativeSelectionBookmark( sel );
181
182 // Correct start offset anticipating the removal of FC.
183 if ( sel.anchorNode == fillingChar.$ && sel.anchorOffset > fillingCharSeqLength ) {
184 bm[ 0 ].offset -= fillingCharSeqLength;
185 }
181 186
182 // Anticipate the offset change brought by the removed char. 187 // Correct end offset anticipating the removal of FC.
183 var startAffected = sel.anchorNode == fillingChar.$ && sel.anchorOffset > 0, 188 if ( sel.focusNode == fillingChar.$ && sel.focusOffset > fillingCharSeqLength ) {
184 endAffected = sel.focusNode == fillingChar.$ && sel.focusOffset > 0; 189 bm[ 1 ].offset -= fillingCharSeqLength;
185 startAffected && bm[ 0 ].offset--; 190 }
186 endAffected && bm[ 1 ].offset--;
187 } 191 }
188 } 192 }
189 193
190 // We can't simply remove the filling node because the user 194 // We can't simply remove the filling node because the user
191 // will actually enlarge it when typing, so we just remove the 195 // will actually enlarge it when typing, so we just remove the
192 // invisible char from it. 196 // invisible char from it.
193 fillingChar.setText( replaceFillingChar( fillingChar.getText() ) ); 197 fillingChar.setText( removeFillingCharSequenceString( fillingChar.getText(), 1 ) );
194 198
195 // Restore the bookmark preserving selection's direction. 199 // Restore the bookmark preserving selection's direction.
196 if ( bm ) { 200 if ( bm ) {
197 moveNativeSelectionToBookmark( element.getDocument().$, bm ); 201 moveNativeSelectionToBookmark( editable.getDocument().$, bm );
198 } 202 }
199 } 203 }
200 } 204 }
201 205
202 function replaceFillingChar( html ) { 206 // #13816
203 return html.replace( /\u200B( )?/g, function( match ) { 207 function removeFillingCharSequenceString( str, nbspAware ) {
204 // #10291 if filling char is followed by a space replace it with nbsp. 208 if ( nbspAware ) {
205 return match[ 1 ] ? '\xa0' : ''; 209 return str.replace( fillingCharSequenceRegExp, function( m, p ) {
206 } ); 210 // #10291 if filling char is followed by a space replace it with NBSP.
211 return p ? '\xa0' : '';
212 } );
213 } else {
214 return str.replace( fillingCharSequence, '' );
215 }
207 } 216 }
208 217
209 function createNativeSelectionBookmark( sel ) { 218 function createNativeSelectionBookmark( sel ) {
@@ -724,7 +733,7 @@
724 case 8: // BACKSPACE 733 case 8: // BACKSPACE
725 case 45: // INS 734 case 45: // INS
726 case 46: // DEl 735 case 46: // DEl
727 removeFillingChar( editable ); 736 removeFillingCharSequenceNode( editable );
728 } 737 }
729 738
730 }, null, null, -1 ); 739 }, null, null, -1 );
@@ -839,65 +848,39 @@
839 } 848 }
840 } ); 849 } );
841 850
842 CKEDITOR.on( 'instanceReady', function( evt ) { 851 // On WebKit only, we need a special "filling" char on some situations
843 var editor = evt.editor, 852 // (#1272). Here we set the events that should invalidate that char.
844 fillingCharBefore, 853 if ( CKEDITOR.env.webkit ) {
845 selectionBookmark; 854 CKEDITOR.on( 'instanceReady', function( evt ) {
855 var editor = evt.editor;
846 856
847 // On WebKit only, we need a special "filling" char on some situations
848 // (#1272). Here we set the events that should invalidate that char.
849 if ( CKEDITOR.env.webkit ) {
850 editor.on( 'selectionChange', function() { 857 editor.on( 'selectionChange', function() {
851 checkFillingChar( editor.editable() ); 858 checkFillingCharSequenceNodeReady( editor.editable() );
852 }, null, null, -1 ); 859 }, null, null, -1 );
860
853 editor.on( 'beforeSetMode', function() { 861 editor.on( 'beforeSetMode', function() {
854 removeFillingChar( editor.editable() ); 862 removeFillingCharSequenceNode( editor.editable() );
855 }, null, null, -1 ); 863 }, null, null, -1 );
856 864
857 editor.on( 'beforeUndoImage', beforeData ); 865 // Filter Undo snapshot's HTML to get rid of Filling Char Sequence.
858 editor.on( 'afterUndoImage', afterData ); 866 // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's
859 editor.on( 'beforeGetData', beforeData, null, null, 0 ); 867 // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (#13816).
860 editor.on( 'getData', afterData ); 868 editor.on( 'getSnapshot', function( evt ) {
861 } 869 if ( evt.data ) {
862 870 evt.data = removeFillingCharSequenceString( evt.data );
863 function beforeData() {
864 var editable = editor.editable();
865 if ( !editable )
866 return;
867
868 var fillingChar = getFillingChar( editable );
869
870 if ( fillingChar ) {
871 // If the selection's focus or anchor is located in the filling char's text node,
872 // we need to restore the selection in afterData, because it will be lost
873 // when setting text. Selection's direction must be preserved.
874 // (#7437, #12489, #12491 comment:3)
875 var sel = editor.document.$.getSelection();
876 if ( sel.type != 'None' && ( sel.anchorNode == fillingChar.$ || sel.focusNode == fillingChar.$ ) )
877 selectionBookmark = createNativeSelectionBookmark( sel );
878
879 fillingCharBefore = fillingChar.getText();
880 fillingChar.setText( replaceFillingChar( fillingCharBefore ) );
881 }
882 }
883
884 function afterData() {
885 var editable = editor.editable();
886 if ( !editable )
887 return;
888
889 var fillingChar = getFillingChar( editable );
890
891 if ( fillingChar ) {
892 fillingChar.setText( fillingCharBefore );
893
894 if ( selectionBookmark ) {
895 moveNativeSelectionToBookmark( editor.document.$, selectionBookmark );
896 selectionBookmark = null;
897 } 871 }
898 } 872 }, editor, null, 20 );
899 } 873
900 } ); 874 // 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,
876 // which should be pruned by the dataProcessor (#13816).
877 // Note: Used low priority to filter when dataProcessor works on strings,
878 // not pseudo–DOM.
879 editor.on( 'toDataFormat', function( evt ) {
880 evt.data.dataValue = removeFillingCharSequenceString( evt.data.dataValue );
881 }, null, null, 0 );
882 } );
883 }
901 884
902 /** 885 /**
903 * Check the selection change in editor and potentially fires 886 * Check the selection change in editor and potentially fires
@@ -1159,6 +1142,25 @@
1159 var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1, 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,
1160 a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 }; 1143 a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 };
1161 1144
1145 // #13816
1146 var fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ),
1147 fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' );
1148
1149 CKEDITOR.tools.extend( CKEDITOR.dom.selection, {
1150 _removeFillingCharSequenceString: removeFillingCharSequenceString,
1151 _createFillingCharSequenceNode: createFillingCharSequenceNode,
1152
1153 /**
1154 * The sequence used in a WebKit-based browser to create a Filling Character. By default it is
1155 * a string of 7 zero-width space characters (U+200B).
1156 *
1157 * @since 4.5.7
1158 * @readonly
1159 * @property {String}
1160 */
1161 FILLING_CHAR_SEQUENCE: fillingCharSequence
1162 } );
1163
1162 CKEDITOR.dom.selection.prototype = { 1164 CKEDITOR.dom.selection.prototype = {
1163 /** 1165 /**
1164 * Gets the native selection object from the browser. 1166 * Gets the native selection object from the browser.
@@ -1899,7 +1901,7 @@
1899 if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { 1901 if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) {
1900 // Append a zero-width space so WebKit will not try to 1902 // Append a zero-width space so WebKit will not try to
1901 // move the selection by itself (#1272). 1903 // move the selection by itself (#1272).
1902 var fillingChar = createFillingChar( this.root ); 1904 var fillingChar = createFillingCharSequenceNode( this.root );
1903 range.insertNode( fillingChar ); 1905 range.insertNode( fillingChar );
1904 1906
1905 next = fillingChar.getNext(); 1907 next = fillingChar.getNext();
@@ -1908,7 +1910,7 @@
1908 // having something before it, it'll not blink. 1910 // having something before it, it'll not blink.
1909 // Let's remove it in this case. 1911 // Let's remove it in this case.
1910 if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' ) { 1912 if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' ) {
1911 removeFillingChar( this.root ); 1913 removeFillingCharSequenceNode( this.root );
1912 range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START ); 1914 range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START );
1913 } else { 1915 } else {
1914 range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END ); 1916 range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END );