/*\r
-Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
});\r
\r
/**\r
- * Registers a function to be called whenever a style changes its state in the\r
+ * Registers a function to be called whenever the selection position changes in the\r
* editing area. The current state is passed to the function. The possible\r
* states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.\r
* @param {CKEDITOR.style} style The style to be watched.\r
- * @param {Function} callback The function to be called when the style state changes.\r
+ * @param {Function} callback The function to be called.\r
* @example\r
* // Create a style object for the <b> element.\r
* var style = new CKEDITOR.style( { element : 'b' } );\r
// callback.\r
var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;\r
\r
- // If the state changed since the last check.\r
- if ( callback.state !== currentState )\r
- {\r
- // Call the callback function, passing the current\r
- // state to it.\r
- callback.fn.call( this, currentState );\r
-\r
- // Save the current state, so it can be compared next\r
- // time.\r
- callback.state = currentState;\r
- }\r
+ // Call the callback function, passing the current\r
+ // state to it.\r
+ callback.fn.call( this, currentState );\r
}\r
});\r
}\r
\r
(function()\r
{\r
- var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };\r
- var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,th:1,ul:1,dl:1,dt:1,dd:1,form:1};\r
+ var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,time:1,meter:1,menu:1,command:1,keygen:1,output:1,progress:1,details:1,datagrid:1,datalist:1 },\r
+ objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,th:1,ul:1,dl:1,dt:1,dd:1,form:1,audio:1,video:1 };\r
\r
- var semicolonFixRegex = /\s*(?:;\s*|$)/;\r
+ var semicolonFixRegex = /\s*(?:;\s*|$)/,\r
+ varRegex = /#\((.+?)\)/g;\r
\r
- var notBookmark = CKEDITOR.dom.walker.bookmark( 0, 1 );\r
+ var notBookmark = CKEDITOR.dom.walker.bookmark( 0, 1 ),\r
+ nonWhitespaces = CKEDITOR.dom.walker.whitespaces( 1 );\r
\r
CKEDITOR.style = function( styleDefinition, variablesValues )\r
{\r
+ // Inline style text as attribute should be converted\r
+ // to styles object.\r
+ var attrs = styleDefinition.attributes;\r
+ if ( attrs && attrs.style )\r
+ {\r
+ styleDefinition.styles = CKEDITOR.tools.extend( {},\r
+ styleDefinition.styles, parseStyleText( attrs.style ) );\r
+ delete attrs.style;\r
+ }\r
+\r
if ( variablesValues )\r
{\r
styleDefinition = CKEDITOR.tools.clone( styleDefinition );\r
replaceVariables( styleDefinition.styles, variablesValues );\r
}\r
\r
- var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();\r
+ var element = this.element = styleDefinition.element ?\r
+ ( typeof styleDefinition.element == 'string' ? styleDefinition.element.toLowerCase() : styleDefinition.element )\r
+ : '*';\r
\r
this.type =\r
- ( element == '#' || blockElements[ element ] ) ?\r
+ blockElements[ element ] ?\r
CKEDITOR.STYLE_BLOCK\r
: objectElements[ element ] ?\r
CKEDITOR.STYLE_OBJECT\r
:\r
CKEDITOR.STYLE_INLINE;\r
\r
+ // If the 'element' property is an object with a set of possible element, it will be applied like an object style: only to existing elements\r
+ if ( typeof this.element == 'object' )\r
+ this.type = CKEDITOR.STYLE_OBJECT;\r
+\r
this._ =\r
{\r
definition : styleDefinition\r
&& ( element == elementPath.block || element == elementPath.blockLimit ) )\r
continue;\r
\r
- if( this.type == CKEDITOR.STYLE_OBJECT\r
- && !( element.getName() in objectElements ) )\r
+ if( this.type == CKEDITOR.STYLE_OBJECT )\r
+ {\r
+ var name = element.getName();\r
+ if ( !( typeof this.element == 'string' ? name == this.element : name in this.element ) )\r
continue;\r
+ }\r
\r
if ( this.checkElementRemovable( element, true ) )\r
return true;\r
return true;\r
},\r
\r
- // Checks if an element, or any of its attributes, is removable by the\r
- // current style definition.\r
- checkElementRemovable : function( element, fullMatch )\r
+ // Check if the element matches the current style definition.\r
+ checkElementMatch : function( element, fullMatch )\r
{\r
- if ( !element )\r
+ var def = this._.definition;\r
+\r
+ if ( !element || !def.ignoreReadonly && element.isReadOnly() )\r
return false;\r
\r
- var def = this._.definition,\r
- attribs;\r
+ var attribs,\r
+ name = element.getName();\r
\r
// If the element name is the same as the style name.\r
- if ( element.getName() == this.element )\r
+ if ( typeof this.element == 'string' ? name == this.element : name in this.element )\r
{\r
// If no attributes are defined in the element.\r
if ( !fullMatch && !element.hasAttributes() )\r
return true;\r
}\r
\r
- // Check if the element can be somehow overriden.\r
+ return false;\r
+ },\r
+\r
+ // Checks if an element, or any of its attributes, is removable by the\r
+ // current style definition.\r
+ checkElementRemovable : function( element, fullMatch )\r
+ {\r
+ // Check element matches the style itself.\r
+ if ( this.checkElementMatch( element, fullMatch ) )\r
+ return true;\r
+\r
+ // Check if the element matches the style overrides.\r
var override = getOverrides( this )[ element.getName() ] ;\r
if ( override )\r
{\r
+ var attribs, attName;\r
+\r
// If no attributes have been defined, remove the element.\r
if ( !( attribs = override.attributes ) )\r
return true;\r
},\r
\r
// Builds the preview HTML based on the styles definition.\r
- buildPreview : function()\r
+ buildPreview : function( label )\r
{\r
var styleDefinition = this._.definition,\r
html = [],\r
if ( cssStyle )\r
html.push( ' style="', cssStyle, '"' );\r
\r
- html.push( '>', styleDefinition.name, '</', elementName, '>' );\r
+ html.push( '>', ( label || styleDefinition.name ), '</', elementName, '>' );\r
\r
return html.join( '' );\r
}\r
var isUnknownElement;\r
\r
// Indicates that fully selected read-only elements are to be included in the styling range.\r
- var includeReadonly = def.includeReadonly;\r
+ var ignoreReadonly = def.ignoreReadonly,\r
+ includeReadonly = ignoreReadonly || def.includeReadonly;\r
\r
// If the read-only inclusion is not available in the definition, try\r
// to get it from the document data.\r
\r
var styleRange;\r
\r
- // Check if the boundaries are inside non stylable elements.\r
- var firstUnstylable = getUnstylableParent( firstNode ),\r
- lastUnstylable = getUnstylableParent( lastNode );\r
-\r
- // If the first element can't be styled, we'll start processing right\r
- // after its unstylable root.\r
- if ( firstUnstylable )\r
- currentNode = firstUnstylable.getNextSourceNode( true );\r
-\r
- // If the last element can't be styled, we'll stop processing on its\r
- // unstylable root.\r
- if ( lastUnstylable )\r
- lastNode = lastUnstylable;\r
+ if ( !ignoreReadonly )\r
+ {\r
+ // Check if the boundaries are inside non stylable elements.\r
+ var firstUnstylable = getUnstylableParent( firstNode ),\r
+ lastUnstylable = getUnstylableParent( lastNode );\r
+\r
+ // If the first element can't be styled, we'll start processing right\r
+ // after its unstylable root.\r
+ if ( firstUnstylable )\r
+ currentNode = firstUnstylable.getNextSourceNode( true );\r
+\r
+ // If the last element can't be styled, we'll stop processing on its\r
+ // unstylable root.\r
+ if ( lastUnstylable )\r
+ lastNode = lastUnstylable;\r
+ }\r
\r
// Do nothing if the current node now follows the last node to be processed.\r
if ( currentNode.getPosition( lastNode ) == CKEDITOR.POSITION_FOLLOWING )\r
* them before removal.\r
*/\r
element.mergeSiblings();\r
- removeFromElement( this, element );\r
-\r
+ if ( element.getName() == this.element )\r
+ removeFromElement( this, element );\r
+ else\r
+ removeOverrides( element, getOverrides( this )[ element.getName() ] );\r
}\r
}\r
}\r
breakNodes();\r
\r
// Now, do the DFS walk.\r
- var currentNode = startNode.getNext();\r
+ var currentNode = startNode;\r
while ( !currentNode.equals( endNode ) )\r
{\r
/*\r
}\r
\r
range.moveToBookmark( bookmark );\r
-}\r
+ }\r
\r
function applyObjectStyle( range )\r
{\r
var root = range.getCommonAncestor( true, true ),\r
- element = root.getAscendant( this.element, true );\r
- element && setupElement( element, this );\r
+ element = root.getAscendant( this.element, true );\r
+ element && !element.isReadOnly() && setupElement( element, this );\r
}\r
\r
function removeObjectStyle( range )\r
{\r
var root = range.getCommonAncestor( true, true ),\r
- element = root.getAscendant( this.element, true );\r
+ element = root.getAscendant( this.element, true );\r
\r
if ( !element )\r
return;\r
\r
- var style = this;\r
- var def = style._.definition;\r
- var attributes = def.attributes;\r
- var styles = CKEDITOR.style.getStyleText( def );\r
+ var style = this,\r
+ def = style._.definition,\r
+ attributes = def.attributes;\r
\r
// Remove all defined attributes.\r
if ( attributes )\r
\r
while ( ( block = iterator.getNextParagraph() ) ) // Only one =\r
{\r
- var newBlock = getElement( this, doc, block );\r
- replaceBlock( block, newBlock );\r
+ if ( !block.isReadOnly() )\r
+ {\r
+ var newBlock = getElement( this, doc, block );\r
+ replaceBlock( block, newBlock );\r
+ }\r
}\r
\r
range.moveToBookmark( bookmark );\r
removeNoAttribsElement( newBlock );\r
}\r
\r
- var nonWhitespaces = CKEDITOR.dom.walker.whitespaces( 1 );\r
/**\r
* Merge a <pre> block with a previous sibling if available.\r
*/\r
} );\r
return headBookmark + str.replace( regexp, replacement ) + tailBookmark;\r
}\r
+\r
/**\r
* Converting a list of <pre> into blocks with format well preserved.\r
*/\r
function removeFromElement( style, element )\r
{\r
var def = style._.definition,\r
- attributes = CKEDITOR.tools.extend( {}, def.attributes, getOverrides( style )[ element.getName() ] ),\r
+ attributes = def.attributes,\r
styles = def.styles,\r
+ overrides = getOverrides( style )[ element.getName() ],\r
// If the style is only about the element itself, we have to remove the element.\r
removeEmpty = CKEDITOR.tools.isEmpty( attributes ) && CKEDITOR.tools.isEmpty( styles );\r
\r
element.removeStyle( styleName );\r
}\r
\r
+ // Remove overrides, but don't remove the element if it's a block element\r
+ removeOverrides( element, overrides, blockElements[ element.getName() ] ) ;\r
+\r
if ( removeEmpty )\r
{\r
!CKEDITOR.dtd.$block[ element.getName() ] || style._.enterMode == CKEDITOR.ENTER_BR && !element.hasAttributes() ?\r
var def = style._.definition,\r
attribs = def.attributes,\r
styles = def.styles,\r
- overrides = getOverrides( style );\r
-\r
- var innerElements = element.getElementsByTag( style.element );\r
+ overrides = getOverrides( style ),\r
+ innerElements = element.getElementsByTag( style.element );\r
\r
for ( var i = innerElements.count(); --i >= 0 ; )\r
removeFromElement( style, innerElements.getItem( i ) );\r
}\r
}\r
}\r
-\r
}\r
\r
/**\r
* Note: Remove the element if no attributes remain.\r
* @param {Object} element\r
* @param {Object} overrides\r
+ * @param {Boolean} Don't remove the element\r
*/\r
- function removeOverrides( element, overrides )\r
+ function removeOverrides( element, overrides, dontRemove )\r
{\r
var attributes = overrides && overrides.attributes ;\r
\r
}\r
}\r
\r
- removeNoAttribsElement( element );\r
+ if ( !dontRemove )\r
+ removeNoAttribsElement( element );\r
}\r
\r
// If the element has no more attributes, remove it.\r
\r
function getElement( style, targetDocument, element )\r
{\r
- var el;\r
-\r
- var def = style._.definition;\r
-\r
- var elementName = style.element;\r
+ var el,\r
+ def = style._.definition,\r
+ elementName = style.element;\r
\r
// The "*" element name will always be a span for this function.\r
if ( elementName == '*' )\r
\r
function setupElement( el, style )\r
{\r
- var def = style._.definition;\r
- var attributes = def.attributes;\r
- var styles = CKEDITOR.style.getStyleText( def );\r
+ var def = style._.definition,\r
+ attributes = def.attributes,\r
+ styles = CKEDITOR.style.getStyleText( def );\r
\r
// Assign all defined attributes.\r
if ( attributes )\r
return el;\r
}\r
\r
- var varRegex = /#\((.+?)\)/g;\r
function replaceVariables( list, variablesValues )\r
{\r
for ( var item in list )\r
}\r
}\r
\r
-\r
// Returns an object that can be used for style matching comparison.\r
// Attributes names and values are all lowercased, and the styles get\r
// merged with the style attribute.\r
else\r
styleText = unparsedCssText;\r
\r
+ // Normalize font-family property, ignore quotes and being case insensitive. (#7322)\r
+ // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property\r
+ styleText = styleText.replace( /(font-family:)(.*?)(?=;|$)/, function ( match, prop, val )\r
+ {\r
+ var names = val.split( ',' );\r
+ for ( var i = 0; i < names.length; i++ )\r
+ names[ i ] = CKEDITOR.tools.trim( names[ i ].replace( /["']/g, '' ) );\r
+ return prop + names.join( ',' );\r
+ });\r
+\r
// Shrinking white-spaces around colon and semi-colon (#4147).\r
// Compensate tail semi-colon.\r
return styleText.replace( /\s*([;:])\s*/, '$1' )\r
function applyStyle( document, remove )\r
{\r
var selection = document.getSelection(),\r
+ // Bookmark the range so we can re-select it after processing.\r
+ bookmarks = selection.createBookmarks( 1 ),\r
ranges = selection.getRanges(),\r
func = remove ? this.removeFromRange : this.applyToRange,\r
range;\r
while ( ( range = iterator.getNextRange() ) )\r
func.call( this, range );\r
\r
- selection.selectRanges( ranges );\r
+ if ( bookmarks.length == 1 && bookmarks[ 0 ].collapsed )\r
+ {\r
+ selection.selectRanges( ranges );\r
+ document.getById( bookmarks[ 0 ].startNode ).remove();\r
+ }\r
+ else\r
+ selection.selectBookmarks( bookmarks );\r
\r
document.removeCustomData( 'doc_processing_style' );\r
}\r
return !!doc;\r
};\r
\r
+/**\r
+ * Manages styles registration and loading. See also {@link CKEDITOR.config.stylesSet}.\r
+ * @namespace\r
+ * @augments CKEDITOR.resourceManager\r
+ * @constructor\r
+ * @since 3.2\r
+ * @example\r
+ * // The set of styles for the <b>Styles</b> combo\r
+ * CKEDITOR.stylesSet.add( 'default',\r
+ * [\r
+ * // Block Styles\r
+ * { name : 'Blue Title' , element : 'h3', styles : { 'color' : 'Blue' } },\r
+ * { name : 'Red Title' , element : 'h3', styles : { 'color' : 'Red' } },\r
+ *\r
+ * // Inline Styles\r
+ * { name : 'Marker: Yellow' , element : 'span', styles : { 'background-color' : 'Yellow' } },\r
+ * { name : 'Marker: Green' , element : 'span', styles : { 'background-color' : 'Lime' } },\r
+ *\r
+ * // Object Styles\r
+ * {\r
+ * name : 'Image on Left',\r
+ * element : 'img',\r
+ * attributes :\r
+ * {\r
+ * 'style' : 'padding: 5px; margin-right: 5px',\r
+ * 'border' : '2',\r
+ * 'align' : 'left'\r
+ * }\r
+ * }\r
+ * ]);\r
+ */\r
CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' );\r
\r
// Backward compatibility (#5025).\r