var initialEnterMode = this._.enterMode;
// Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode
var initialEnterMode = this._.enterMode;
// Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode
// Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we
// use _.enterMode for backward compatibility with those hacks.
// Note: we should not change style's enter mode if it was already set.
// Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we
// use _.enterMode for backward compatibility with those hacks.
// Note: we should not change style's enter mode if it was already set.
var styleVal = stylesDef[ style ],
text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
var styleVal = stylesDef[ style ],
text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
range.shrink( CKEDITOR.SHRINK_TEXT );
// Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside.
range.shrink( CKEDITOR.SHRINK_TEXT );
// Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside.
range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 );
var bookmark = range.createBookmark(),
range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 );
var bookmark = range.createBookmark(),
for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) {
// 1. If it's collaped inside text nodes, try to remove the style from the whole element.
//
// 2. Otherwise if it's collapsed on element boundaries, moving the selection
// outside the styles instead of removing the whole tag,
for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) {
// 1. If it's collaped inside text nodes, try to remove the style from the whole element.
//
// 2. Otherwise if it's collapsed on element boundaries, moving the selection
// outside the styles instead of removing the whole tag,
- // also make sure other inner styles were well preserverd.(#3309)
- if ( element == startPath.block || element == startPath.blockLimit )
+ // also make sure other inner styles were well preserved.(http://dev.ckeditor.com/ticket/3309)
+ //
+ // 3. Force removing the element even if it's an boundary element when alwaysRemoveElement is true.
+ // Without it, the links won't be unlinked if the cursor is placed right before/after it. (http://dev.ckeditor.com/ticket/13062)
+ if ( element == startPath.block || element == startPath.blockLimit ) {
- if ( range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) {
+ if ( !alwaysRemoveElement && range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) {
var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ),
element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 );
var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ),
element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 );
// Replace the original block with new one, with special treatment
// for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
// Replace the original block with new one, with special treatment
// for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
function replaceBlock( block, newBlock ) {
// Block is to be removed, create a temp element to
// save contents.
function replaceBlock( block, newBlock ) {
// Block is to be removed, create a temp element to
// save contents.
if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) )
continue;
if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) )
continue;
// @returns {Boolean}
function compareCssText( source, target ) {
function filter( string, propertyName ) {
// @returns {Boolean}
function compareCssText( source, target ) {
function filter( string, propertyName ) {
var doc = selection.document,
ranges = selection.getRanges(),
func = remove ? this.removeFromRange : this.applyToRange,
var doc = selection.document,
ranges = selection.getRanges(),
func = remove ? this.removeFromRange : this.applyToRange,
+ originalRanges,
+ range,
+ i;
+
+ // In case of fake table selection, we would like to apply all styles and then select
+ // the original ranges. Otherwise browsers would complain about discontiguous selection.
+ if ( selection.isFake && selection.isInTable() ) {
+ originalRanges = [];
+
+ for ( i = 0; i < ranges.length; i++ ) {
+ originalRanges.push( ranges[ i ].clone() );
+ }
+ }
var iterator = ranges.createIterator();
while ( ( range = iterator.getNextRange() ) )
func.call( this, range, editor );
var iterator = ranges.createIterator();
while ( ( range = iterator.getNextRange() ) )
func.call( this, range, editor );
CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
CKEDITOR.loadStylesSet = function( name, url, callback ) {
CKEDITOR.stylesSet.addExternal( name, url, '' );
CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
CKEDITOR.loadStylesSet = function( name, url, callback ) {
CKEDITOR.stylesSet.addExternal( name, url, '' );
if ( configStyleSet instanceof Array ) {
editor._.stylesDefinitions = configStyleSet;
callback( configStyleSet );
if ( configStyleSet instanceof Array ) {
editor._.stylesDefinitions = configStyleSet;
callback( configStyleSet );