-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
return false;
}
- function createFillingChar( element ) {
- removeFillingChar( element, false );
+ function createFillingCharSequenceNode( editable ) {
+ removeFillingCharSequenceNode( editable, false );
- var fillingChar = element.getDocument().createText( '\u200B' );
- element.setCustomData( 'cke-fillingChar', fillingChar );
+ var fillingChar = editable.getDocument().createText( fillingCharSequence );
+ editable.setCustomData( 'cke-fillingChar', fillingChar );
return fillingChar;
}
- function getFillingChar( element ) {
- return element.getCustomData( 'cke-fillingChar' );
- }
-
// Checks if a filling char has been used, eventualy removing it (#1272).
- function checkFillingChar( element ) {
- var fillingChar = getFillingChar( element );
+ function checkFillingCharSequenceNodeReady( editable ) {
+ var fillingChar = editable.getCustomData( 'cke-fillingChar' );
+
if ( fillingChar ) {
// Use this flag to avoid removing the filling char right after
// creating it.
- if ( fillingChar.getCustomData( 'ready' ) )
- removeFillingChar( element );
- else
+ if ( fillingChar.getCustomData( 'ready' ) ) {
+ removeFillingCharSequenceNode( editable );
+ } else {
fillingChar.setCustomData( 'ready', 1 );
+ }
}
}
- function removeFillingChar( element, keepSelection ) {
- var fillingChar = element && element.removeCustomData( 'cke-fillingChar' );
- if ( fillingChar ) {
+ function removeFillingCharSequenceNode( editable, keepSelection ) {
+ var fillingChar = editable && editable.removeCustomData( 'cke-fillingChar' );
+ if ( fillingChar ) {
// Text selection position might get mangled by
// subsequent dom modification, save it now for restoring. (#8617)
if ( keepSelection !== false ) {
- var bm,
- sel = element.getDocument().getSelection().getNative(),
+ var sel = editable.getDocument().getSelection().getNative(),
// Be error proof.
- range = sel && sel.type != 'None' && sel.getRangeAt( 0 );
+ range = sel && sel.type != 'None' && sel.getRangeAt( 0 ),
+ fillingCharSeqLength = fillingCharSequence.length;
- if ( fillingChar.getLength() > 1 && range && range.intersectsNode( fillingChar.$ ) ) {
- bm = createNativeSelectionBookmark( sel );
+ // If there's some text other than the sequence in the FC text node and the range
+ // intersects with that node...
+ if ( fillingChar.getLength() > fillingCharSeqLength && range && range.intersectsNode( fillingChar.$ ) ) {
+ var bm = createNativeSelectionBookmark( sel );
+
+ // Correct start offset anticipating the removal of FC.
+ if ( sel.anchorNode == fillingChar.$ && sel.anchorOffset > fillingCharSeqLength ) {
+ bm[ 0 ].offset -= fillingCharSeqLength;
+ }
- // Anticipate the offset change brought by the removed char.
- var startAffected = sel.anchorNode == fillingChar.$ && sel.anchorOffset > 0,
- endAffected = sel.focusNode == fillingChar.$ && sel.focusOffset > 0;
- startAffected && bm[ 0 ].offset--;
- endAffected && bm[ 1 ].offset--;
+ // Correct end offset anticipating the removal of FC.
+ if ( sel.focusNode == fillingChar.$ && sel.focusOffset > fillingCharSeqLength ) {
+ bm[ 1 ].offset -= fillingCharSeqLength;
+ }
}
}
// We can't simply remove the filling node because the user
// will actually enlarge it when typing, so we just remove the
// invisible char from it.
- fillingChar.setText( replaceFillingChar( fillingChar.getText() ) );
+ fillingChar.setText( removeFillingCharSequenceString( fillingChar.getText(), 1 ) );
// Restore the bookmark preserving selection's direction.
if ( bm ) {
- moveNativeSelectionToBookmark( element.getDocument().$, bm );
+ moveNativeSelectionToBookmark( editable.getDocument().$, bm );
}
}
}
- function replaceFillingChar( html ) {
- return html.replace( /\u200B( )?/g, function( match ) {
- // #10291 if filling char is followed by a space replace it with nbsp.
- return match[ 1 ] ? '\xa0' : '';
- } );
+ // #13816
+ function removeFillingCharSequenceString( str, nbspAware ) {
+ if ( nbspAware ) {
+ return str.replace( fillingCharSequenceRegExp, function( m, p ) {
+ // #10291 if filling char is followed by a space replace it with NBSP.
+ return p ? '\xa0' : '';
+ } );
+ } else {
+ return str.replace( fillingCharSequence, '' );
+ }
}
function createNativeSelectionBookmark( sel ) {
case 8: // BACKSPACE
case 45: // INS
case 46: // DEl
- removeFillingChar( editable );
+ removeFillingCharSequenceNode( editable );
}
}, null, null, -1 );
}
} );
- CKEDITOR.on( 'instanceReady', function( evt ) {
- var editor = evt.editor,
- fillingCharBefore,
- selectionBookmark;
+ // On WebKit only, we need a special "filling" char on some situations
+ // (#1272). Here we set the events that should invalidate that char.
+ if ( CKEDITOR.env.webkit ) {
+ CKEDITOR.on( 'instanceReady', function( evt ) {
+ var editor = evt.editor;
- // On WebKit only, we need a special "filling" char on some situations
- // (#1272). Here we set the events that should invalidate that char.
- if ( CKEDITOR.env.webkit ) {
editor.on( 'selectionChange', function() {
- checkFillingChar( editor.editable() );
+ checkFillingCharSequenceNodeReady( editor.editable() );
}, null, null, -1 );
+
editor.on( 'beforeSetMode', function() {
- removeFillingChar( editor.editable() );
+ removeFillingCharSequenceNode( editor.editable() );
}, null, null, -1 );
- editor.on( 'beforeUndoImage', beforeData );
- editor.on( 'afterUndoImage', afterData );
- editor.on( 'beforeGetData', beforeData, null, null, 0 );
- editor.on( 'getData', afterData );
- }
-
- function beforeData() {
- var editable = editor.editable();
- if ( !editable )
- return;
-
- var fillingChar = getFillingChar( editable );
-
- if ( fillingChar ) {
- // If the selection's focus or anchor is located in the filling char's text node,
- // we need to restore the selection in afterData, because it will be lost
- // when setting text. Selection's direction must be preserved.
- // (#7437, #12489, #12491 comment:3)
- var sel = editor.document.$.getSelection();
- if ( sel.type != 'None' && ( sel.anchorNode == fillingChar.$ || sel.focusNode == fillingChar.$ ) )
- selectionBookmark = createNativeSelectionBookmark( sel );
-
- fillingCharBefore = fillingChar.getText();
- fillingChar.setText( replaceFillingChar( fillingCharBefore ) );
- }
- }
-
- function afterData() {
- var editable = editor.editable();
- if ( !editable )
- return;
-
- var fillingChar = getFillingChar( editable );
-
- if ( fillingChar ) {
- fillingChar.setText( fillingCharBefore );
-
- if ( selectionBookmark ) {
- moveNativeSelectionToBookmark( editor.document.$, selectionBookmark );
- selectionBookmark = null;
+ // Filter Undo snapshot's HTML to get rid of Filling Char Sequence.
+ // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's
+ // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (#13816).
+ editor.on( 'getSnapshot', function( evt ) {
+ if ( evt.data ) {
+ evt.data = removeFillingCharSequenceString( evt.data );
}
- }
- }
- } );
+ }, editor, null, 20 );
+
+ // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat
+ // instead of #getData because once removed, FCSeq may leave an empty element,
+ // which should be pruned by the dataProcessor (#13816).
+ // Note: Used low priority to filter when dataProcessor works on strings,
+ // not pseudo–DOM.
+ editor.on( 'toDataFormat', function( evt ) {
+ evt.data.dataValue = removeFillingCharSequenceString( evt.data.dataValue );
+ }, null, null, 0 );
+ } );
+ }
/**
* Check the selection change in editor and potentially fires
var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1,
a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 };
+ // #13816
+ var fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ),
+ fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' );
+
+ CKEDITOR.tools.extend( CKEDITOR.dom.selection, {
+ _removeFillingCharSequenceString: removeFillingCharSequenceString,
+ _createFillingCharSequenceNode: createFillingCharSequenceNode,
+
+ /**
+ * The sequence used in a WebKit-based browser to create a Filling Character. By default it is
+ * a string of 7 zero-width space characters (U+200B).
+ *
+ * @since 4.5.7
+ * @readonly
+ * @property {String}
+ */
+ FILLING_CHAR_SEQUENCE: fillingCharSequence
+ } );
+
CKEDITOR.dom.selection.prototype = {
/**
* Gets the native selection object from the browser.
if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) {
// Append a zero-width space so WebKit will not try to
// move the selection by itself (#1272).
- var fillingChar = createFillingChar( this.root );
+ var fillingChar = createFillingCharSequenceNode( this.root );
range.insertNode( fillingChar );
next = fillingChar.getNext();
// having something before it, it'll not blink.
// Let's remove it in this case.
if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' ) {
- removeFillingChar( this.root );
+ removeFillingCharSequenceNode( this.root );
range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START );
} else {
range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END );