X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=_source%2Fplugins%2Fstyles%2Fplugin.js;h=eade43b36e2ce5532d35b403e8c77e9d495a615e;hb=9afde8772159bd3436f1f5b7862960307710ae5a;hp=2d9878b5611541f42fd4385faf312244a747fba6;hpb=8665a7c6c60586526e32e8941fe2896739b6ebfb;p=ckeditor.git diff --git a/_source/plugins/styles/plugin.js b/_source/plugins/styles/plugin.js index 2d9878b..eade43b 100644 --- a/_source/plugins/styles/plugin.js +++ b/_source/plugins/styles/plugin.js @@ -5,15 +5,25 @@ For licensing, see LICENSE.html or http://ckeditor.com/license CKEDITOR.plugins.add( 'styles', { - requires : [ 'selection' ] + requires : [ 'selection' ], + init : function( editor ) + { + // This doesn't look like correct, but it's the safest way to proper + // pass the disableReadonlyStyling configuration to the style system + // without having to change any method signature in the API. (#6103) + editor.on( 'contentDom', function() + { + editor.document.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); + }); + } }); /** * Registers a function to be called whenever a style changes its state in the * editing area. The current state is passed to the function. The possible * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}. - * @param {CKEDITOR.style} The style to be watched. - * @param {Function} The function to be called when the style state changes. + * @param {CKEDITOR.style} style The style to be watched. + * @param {Function} callback The function to be called when the style state changes. * @example * // Create a style object for the <b> element. * var style = new CKEDITOR.style( { element : 'b' } ); @@ -60,13 +70,13 @@ CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback ) // Save the current state, so it can be compared next // time. - callback.state !== currentState; + callback.state = currentState; } } }); } - // Save the callback info, so it can be checked on the next occurence of + // Save the callback info, so it can be checked on the next occurrence of // selectionChange. styleStateChangeCallbacks.push( { style : style, fn : callback } ); }; @@ -137,6 +147,8 @@ CKEDITOR.STYLE_OBJECT = 3; return ( this.removeFromRange = this.type == CKEDITOR.STYLE_INLINE ? removeInlineStyle + : this.type == CKEDITOR.STYLE_OBJECT ? + removeObjectStyle : null ).call( this, range ); }, @@ -180,6 +192,10 @@ CKEDITOR.STYLE_OBJECT = 3; return false; }, + /** + * Whether this style can be applied at the element path. + * @param elementPath + */ checkApplicable : function( elementPath ) { switch ( this.type ) @@ -222,6 +238,8 @@ CKEDITOR.STYLE_OBJECT = 3; continue; var elementAttr = element.getAttribute( attName ) || ''; + + // Special treatment for 'style' attribute is required. if ( attName == 'style' ? compareCssText( attribs[ attName ], normalizeCssText( elementAttr, false ) ) : attribs[ attName ] == elementAttr ) @@ -345,6 +363,35 @@ CKEDITOR.STYLE_OBJECT = 3; return ( styleDefinition._ST = stylesText ); }; + // Gets the parent element which blocks the styling for an element. This + // can be done through read-only elements (contenteditable=false) or + // elements with the "data-cke-nostyle" attribute. + function getUnstylableParent( element ) + { + var unstylable, + editable; + + while ( ( element = element.getParent() ) ) + { + if ( element.getName() == 'body' ) + break; + + if ( element.getAttribute( 'data-cke-nostyle' ) ) + unstylable = element; + else if ( !editable ) + { + var contentEditable = element.getAttribute( 'contentEditable' ); + + if ( contentEditable == 'false' ) + unstylable = element; + else if ( contentEditable == 'true' ) + editable = 1; + } + } + + return unstylable; + } + function applyInlineStyle( range ) { var document = range.document; @@ -367,12 +414,17 @@ CKEDITOR.STYLE_OBJECT = 3; var def = this._.definition; var isUnknownElement; + // Indicates that fully selected read-only elements are to be included in the styling range. + var includeReadonly = def.includeReadonly; + + // If the read-only inclusion is not available in the definition, try + // to get it from the document data. + if ( includeReadonly == undefined ) + includeReadonly = document.getCustomData( 'cke_includeReadonly' ); + // Get the DTD definition for the element. Defaults to "span". var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span ); - // Bookmark the range so we can re-select it after processing. - var bookmark = range.createBookmark(); - // Expand the range. range.enlarge( CKEDITOR.ENLARGE_ELEMENT ); range.trim(); @@ -387,6 +439,24 @@ CKEDITOR.STYLE_OBJECT = 3; var styleRange; + // Check if the boundaries are inside non stylable elements. + var firstUnstylable = getUnstylableParent( firstNode ), + lastUnstylable = getUnstylableParent( lastNode ); + + // If the first element can't be styled, we'll start processing right + // after its unstylable root. + if ( firstUnstylable ) + currentNode = firstUnstylable.getNextSourceNode( true ); + + // If the last element can't be styled, we'll stop processing on its + // unstylable root. + if ( lastUnstylable ) + lastNode = lastUnstylable; + + // Do nothing if the current node now follows the last node to be processed. + if ( currentNode.getPosition( lastNode ) == CKEDITOR.POSITION_FOLLOWING ) + currentNode = 0; + while ( currentNode ) { var applyStyle = false; @@ -400,8 +470,10 @@ CKEDITOR.STYLE_OBJECT = 3; { var nodeType = currentNode.type; var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null; + var nodeIsReadonly = nodeName && ( currentNode.getAttribute( 'contentEditable' ) == 'false' ); + var nodeIsNoStyle = nodeName && currentNode.getAttribute( 'data-cke-nostyle' ); - if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) ) + if ( nodeName && currentNode.data( 'cke-bookmark' ) ) { currentNode = currentNode.getNextSourceNode( true ); continue; @@ -409,6 +481,8 @@ CKEDITOR.STYLE_OBJECT = 3; // Check if the current node can be a child of the style element. if ( !nodeName || ( dtd[ nodeName ] + && !nodeIsNoStyle + && ( !nodeIsReadonly || includeReadonly ) && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) && ( !def.childRule || def.childRule( currentNode ) ) ) ) { @@ -430,9 +504,9 @@ CKEDITOR.STYLE_OBJECT = 3; styleRange.setStartBefore( currentNode ); } - // Non element nodes, or empty elements can be added - // completely to the range. - if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) ) + // Non element nodes, readonly elements, or empty + // elements can be added completely to the range. + if ( nodeType == CKEDITOR.NODE_TEXT || nodeIsReadonly || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) ) { var includedNode = currentNode; var parentNode; @@ -456,7 +530,6 @@ CKEDITOR.STYLE_OBJECT = 3; // in this style DTD, so apply the style immediately. if ( !includedNode.$.nextSibling ) applyStyle = true; - } } else @@ -466,46 +539,71 @@ CKEDITOR.STYLE_OBJECT = 3; applyStyle = true; // Get the next node to be processed. - currentNode = currentNode.getNextSourceNode(); + currentNode = currentNode.getNextSourceNode( nodeIsNoStyle || nodeIsReadonly ); } // Apply the style if we have something to which apply it. if ( applyStyle && styleRange && !styleRange.collapsed ) { // Build the style element, based on the style object definition. - var styleNode = getElement( this, document ); + var styleNode = getElement( this, document ), + styleHasAttrs = styleNode.hasAttributes(); // Get the element that holds the entire range. var parent = styleRange.getCommonAncestor(); + var removeList = { + styles : {}, + attrs : {}, + // Styles cannot be removed. + blockedStyles : {}, + // Attrs cannot be removed. + blockedAttrs : {} + }; + + var attName, styleName, value; + // Loop through the parents, removing the redundant attributes // from the element to be applied. while ( styleNode && parent ) { if ( parent.getName() == elementName ) { - for ( var attName in def.attributes ) + for ( attName in def.attributes ) { - if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) ) - styleNode.removeAttribute( attName ); - } + if ( removeList.blockedAttrs[ attName ] || !( value = parent.getAttribute( styleName ) ) ) + continue; - for ( var styleName in def.styles ) - { - if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) ) - styleNode.removeStyle( styleName ); + if ( styleNode.getAttribute( attName ) == value ) + removeList.attrs[ attName ] = 1; + else + removeList.blockedAttrs[ attName ] = 1; } - if ( !styleNode.hasAttributes() ) + for ( styleName in def.styles ) { - styleNode = null; - break; + if ( removeList.blockedStyles[ styleName ] || !( value = parent.getStyle( styleName ) ) ) + continue; + + if ( styleNode.getStyle( styleName ) == value ) + removeList.styles[ styleName ] = 1; + else + removeList.blockedStyles[ styleName ] = 1; } } parent = parent.getParent(); } + for ( attName in removeList.attrs ) + styleNode.removeAttribute( attName ); + + for ( styleName in removeList.styles ) + styleNode.removeStyle( styleName ); + + if ( styleHasAttrs && !styleNode.hasAttributes() ) + styleNode = null; + if ( styleNode ) { // Move the contents of the range to the style element. @@ -531,6 +629,15 @@ CKEDITOR.STYLE_OBJECT = 3; if ( !CKEDITOR.env.ie ) styleNode.$.normalize(); } + // Style already inherit from parents, left just to clear up any internal overrides. (#5931) + else + { + styleNode = new CKEDITOR.dom.element( 'span' ); + styleRange.extractContents().appendTo( styleNode ); + styleRange.insertNode( styleNode ); + removeFromInsideElement( this, styleNode ); + styleNode.remove( true ); + } // Style applied, let's release the range, so it gets // re-initialization in the next loop. @@ -538,9 +645,9 @@ CKEDITOR.STYLE_OBJECT = 3; } } - firstNode.remove(); - lastNode.remove(); - range.moveToBookmark( bookmark ); + // Remove the bookmark nodes. + range.moveToBookmark( boundaryNodes ); + // Minimize the result range to exclude empty text nodes. (#5374) range.shrink( CKEDITOR.SHRINK_TEXT ); } @@ -579,12 +686,14 @@ CKEDITOR.STYLE_OBJECT = 3; if ( this.checkElementRemovable( element ) ) { - var endOfElement = range.checkBoundaryOfElement( element, CKEDITOR.END ), - startOfElement = !endOfElement && range.checkBoundaryOfElement( element, CKEDITOR.START ); - if ( startOfElement || endOfElement ) + var isStart; + + if ( range.collapsed && ( + range.checkBoundaryOfElement( element, CKEDITOR.END ) || + ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) { boundaryElement = element; - boundaryElement.match = startOfElement ? 'start' : 'end'; + boundaryElement.match = isStart ? 'start' : 'end'; } else { @@ -714,6 +823,41 @@ CKEDITOR.STYLE_OBJECT = 3; element && setupElement( element, this ); } + function removeObjectStyle( range ) + { + var root = range.getCommonAncestor( true, true ), + element = root.getAscendant( this.element, true ); + + if ( !element ) + return; + + var style = this; + var def = style._.definition; + var attributes = def.attributes; + var styles = CKEDITOR.style.getStyleText( def ); + + // Remove all defined attributes. + if ( attributes ) + { + for ( var att in attributes ) + { + element.removeAttribute( att, attributes[ att ] ); + } + } + + // Assign all defined styles. + if ( def.styles ) + { + for ( var i in def.styles ) + { + if ( !def.styles.hasOwnProperty( i ) ) + continue; + + element.removeStyle( i ); + } + } + } + function applyBlockStyle( range ) { // Serializible bookmarks is needed here since @@ -733,7 +877,7 @@ CKEDITOR.STYLE_OBJECT = 3; while ( ( block = iterator.getNextParagraph() ) ) // Only one = { - var newBlock = getElement( this, doc ); + var newBlock = getElement( this, doc, block ); replaceBlock( block, newBlock ); } @@ -768,13 +912,14 @@ CKEDITOR.STYLE_OBJECT = 3; } } + var nonWhitespaces = CKEDITOR.dom.walker.whitespaces( true ); /** * Merge a
block with a previous sibling if available.
*/
function mergePre( preBlock )
{
var previousBlock;
- if ( !( ( previousBlock = preBlock.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) )
+ if ( !( ( previousBlock = preBlock.getPrevious( nonWhitespaces ) )
&& previousBlock.is
&& previousBlock.is( 'pre') ) )
return;
@@ -806,7 +951,7 @@ CKEDITOR.STYLE_OBJECT = 3;
{
// Exclude the ones at header OR at tail,
// and ignore bookmark content between them.
- var duoBrRegex = /(\S\s*)\n(?:\s|(]+_fck_bookmark.*?\/span>))*\n(?!$)/gi,
+ var duoBrRegex = /(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,
blockName = preBlock.getName(),
splitedHtml = replace( preBlock.getOuterHtml(),
duoBrRegex,
@@ -828,7 +973,7 @@ CKEDITOR.STYLE_OBJECT = 3;
var headBookmark = '',
tailBookmark = '';
- str = str.replace( /(^]+_fck_bookmark.*?\/span>)|(]+_fck_bookmark.*?\/span>$)/gi,
+ str = str.replace( /(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,
function( str, m1, m2 ){
m1 && ( headBookmark = m1 );
m2 && ( tailBookmark = m2 );
@@ -1040,7 +1185,7 @@ CKEDITOR.STYLE_OBJECT = 3;
}
}
- function getElement( style, targetDocument )
+ function getElement( style, targetDocument, element )
{
var el;
@@ -1055,6 +1200,10 @@ CKEDITOR.STYLE_OBJECT = 3;
// Create the element.
el = new CKEDITOR.dom.element( elementName, targetDocument );
+ // #6226: attributes should be copied before the new ones are applied
+ if ( element )
+ element.copyAttributes( el );
+
return setupElement( el, style );
}
@@ -1074,7 +1223,7 @@ CKEDITOR.STYLE_OBJECT = 3;
}
// Assign all defined styles.
- if ( styles )
+ if( styles )
el.setAttribute( 'style', styles );
return el;
@@ -1198,6 +1347,7 @@ CKEDITOR.STYLE_OBJECT = 3;
return overrides;
}
+ // Make the comparison of attribute value easier by standardizing it.
function normalizeProperty( name, value, isStyle )
{
var temp = new CKEDITOR.dom.element( 'span' );
@@ -1205,6 +1355,7 @@ CKEDITOR.STYLE_OBJECT = 3;
return temp[ isStyle ? 'getStyle' : 'getAttribute' ]( name );
}
+ // Make the comparison of style text easier by standardizing it.
function normalizeCssText( unparsedCssText, nativeNormalize )
{
var styleText;
@@ -1223,7 +1374,11 @@ CKEDITOR.STYLE_OBJECT = 3;
// Compensate tail semi-colon.
return styleText.replace( /\s*([;:])\s*/, '$1' )
.replace( /([^\s;])$/, '$1;')
- .replace( /,\s+/g, ',' ) // Trimming spaces after comma (e.g. font-family name)(#4107).
+ // Trimming spaces after comma(#4107),
+ // remove quotations(#6403),
+ // mostly for differences on "font-family".
+ .replace( /,\s+/g, ',' )
+ .replace( /\"/g,'' )
.toLowerCase();
}
@@ -1240,14 +1395,18 @@ CKEDITOR.STYLE_OBJECT = 3;
return retval;
}
+ /**
+ * Compare two bunch of styles, with the speciality that value 'inherit'
+ * is treated as a wildcard which will match any value.
+ * @param {Object|String} source
+ * @param {Object|String} target
+ */
function compareCssText( source, target )
{
typeof source == 'string' && ( source = parseStyleText( source ) );
typeof target == 'string' && ( target = parseStyleText( target ) );
for( var name in source )
{
- // Value 'inherit' is treated as a wildcard,
- // which will match any value.
if ( !( name in target &&
( target[ name ] == source[ name ]
|| source[ name ] == 'inherit'
@@ -1261,17 +1420,24 @@ CKEDITOR.STYLE_OBJECT = 3;
function applyStyle( document, remove )
{
- // Get all ranges from the selection.
- var selection = document.getSelection();
- var ranges = selection.getRanges();
- var func = remove ? this.removeFromRange : this.applyToRange;
-
- // Apply the style to the ranges.
- for ( var i = 0 ; i < ranges.length ; i++ )
- func.call( this, ranges[ i ] );
-
- // Select the ranges again.
- selection.selectRanges( ranges );
+ var selection = document.getSelection(),
+ // Bookmark the range so we can re-select it after processing.
+ bookmarks = selection.createBookmarks( 1 ),
+ ranges = selection.getRanges(),
+ func = remove ? this.removeFromRange : this.applyToRange,
+ range;
+
+ var iterator = ranges.createIterator();
+ while ( ( range = iterator.getNextRange() ) )
+ func.call( this, range );
+
+ if ( bookmarks.length == 1 && bookmarks[0].collapsed )
+ {
+ selection.selectRanges( ranges );
+ document.getById( bookmarks[ 0 ].startNode ).remove();
+ }
+ else
+ selection.selectBookmarks( bookmarks );
}
})();
@@ -1310,7 +1476,7 @@ CKEDITOR.loadStylesSet = function( name, url, callback )
/**
* Gets the current styleSet for this instance
- * @param {Function} The function to be called with the styles data.
+ * @param {Function} callback The function to be called with the styles data.
* @example
* editor.getStylesSet( function( stylesDefinitions ) {} );
*/
@@ -1351,6 +1517,23 @@ CKEDITOR.editor.prototype.getStylesSet = function( callback )
};
/**
+ * Indicates that fully selected read-only elements will be included when
+ * applying the style (for inline styles only).
+ * @name CKEDITOR.style.includeReadonly
+ * @type Boolean
+ * @default false
+ * @since 3.5
+ */
+
+ /**
+ * Disables inline styling on read-only elements.
+ * @name CKEDITOR.config.disableReadonlyStyling
+ * @type Boolean
+ * @default false
+ * @since 3.5
+ */
+
+/**
* The "styles definition set" to use in the editor. They will be used in the
* styles combo and the Style selector of the div container.
* The styles may be defined in the page containing the editor, or can be