diff options
Diffstat (limited to 'sources/core/selection.js')
-rw-r--r-- | sources/core/selection.js | 182 |
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 ); |