Index: /CKEditor/branches/prototype/_source/plugins/styles/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/styles/plugin.js (revision 2618)
+++ /CKEditor/branches/prototype/_source/plugins/styles/plugin.js (revision 2618)
@@ -0,0 +1,498 @@
+/*
+ * CKEditor - The text editor for Internet - http://ckeditor.com
+ * Copyright (C) 2003-2008 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+CKEDITOR.plugins.add( 'styles' );
+
+CKEDITOR.STYLE_BLOCK = 1;
+CKEDITOR.STYLE_INLINE = 2;
+CKEDITOR.STYLE_OBJECT = 3;
+
+(function()
+{
+ var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
+ var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
+
+ CKEDITOR.style = function( styleDefinition )
+ {
+ var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
+
+ this.type =
+ ( element == '#' || blockElements[ element ] ) ?
+ CKEDITOR.STYLE_BLOCK
+ : objectElements[ element ] ?
+ CKEDITOR.STYLE_OBJECT
+ :
+ CKEDITOR.STYLE_INLINE;
+
+ this._ =
+ {
+ definition : styleDefinition
+ };
+ };
+
+ CKEDITOR.style.prototype =
+ {
+ apply : function( document )
+ {
+ // Get all ranges from the selection.
+ var selection = document.getSelection();
+ var ranges = selection.getRanges();
+
+ // Apply the style to the ranges.
+ for ( var i = 0 ; i < ranges.length ; i++ )
+ this.applyToRange( ranges[ i ] );
+
+ // Select the ranges again.
+ selection.selectRanges( ranges );
+ },
+
+ applyToRange : function( range )
+ {
+ return ( this.applyToRange =
+ this.type == CKEDITOR.STYLE_INLINE ?
+ applyInlineStyle
+ : this.type == CKEDITOR.STYLE_BLOCK ?
+ applyBlockStyle
+ : null ).call( this, range );
+ },
+
+ /**
+ * Sets the value of a variable attribute or style, to be used when
+ * appliying the style. This function must be called before using any
+ * other function in this object.
+ */
+ setVariable : function( name, value )
+ {
+ var variables = this._.variables || ( this._variables = {} );
+ variables[ name ] = value;
+ }
+ };
+
+ var applyInlineStyle = function( range )
+ {
+ var document = range.document;
+
+ if ( range.collapsed )
+ {
+ // Create the element to be inserted in the DOM.
+ var collapsedElement = getElement( this, document );
+
+ // Insert the empty element into the DOM at the range position.
+ range.insertNode( collapsedElement );
+
+ // Place the selection right inside the empty element.
+ range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
+
+ return;
+ }
+
+ var elementName = this.element;
+ var def = this._.definition;
+ var isUnknownElement;
+
+ // 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();
+
+ // Get the first node to be processed and the last, which concludes the
+ // processing.
+ var firstNode = range.startContainer.getChild( range.startOffset ) || range.startContainer.getNextSourceNode();
+ var lastNode = range.endContainer.getChild( range.endOffset ) || ( range.endOffset ? range.endContainer.getNextSourceNode() : range.endContainer );
+
+ var currentNode = firstNode;
+
+ var styleRange;
+
+ // Indicates that that some useful inline content has been found, so
+ // the style should be applied.
+ var hasContents;
+
+ while ( currentNode )
+ {
+ var applyStyle = false;
+
+ if ( currentNode.equals( lastNode ) )
+ {
+ currentNode = null;
+ applyStyle = true;
+ }
+ else
+ {
+ var nodeType = currentNode.type;
+ var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
+
+ if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
+ {
+ currentNode = currentNode.getNextSourceNode( true );
+ continue;
+ }
+
+ // Check if the current node can be a child of the style element.
+ if ( !nodeName || ( dtd[ nodeName ] && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) )
+ {
+ var currentParent = currentNode.getParent();
+
+ // Check if the style element can be a child of the current
+ // node parent or if the element is not defined in the DTD.
+ if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
+ {
+ // This node will be part of our range, so if it has not
+ // been started, place its start right before the node.
+ // In the case of an element node, it will be included
+ // only if it is entirely inside the range.
+ if ( !styleRange && ( !nodeName || !CKEDITOR.dtd.$removeEmpty[ nodeName ] || ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) )
+ {
+ styleRange = new CKEDITOR.dom.range( document );
+ 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() && currentNode.$.offsetWidth ) )
+ {
+ var includedNode = currentNode;
+ var parentNode;
+
+ // This node is about to be included completelly, but,
+ // if this is the last node in its parent, we must also
+ // check if the parent itself can be added completelly
+ // to the range.
+ while ( !includedNode.$.nextSibling
+ && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
+ && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
+ {
+ includedNode = parentNode;
+ }
+
+ styleRange.setEndAfter( includedNode );
+
+ // If the included node still is the last node in its
+ // parent, it means that the parent can't be included
+ // in this style DTD, so apply the style immediately.
+ if ( !includedNode.$.nextSibling )
+ applyStyle = true;
+
+ if ( !hasContents )
+ hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
+ }
+ }
+ else
+ applyStyle = true;
+ }
+ else
+ applyStyle = true;
+
+ // Get the next node to be processed.
+ currentNode = currentNode.getNextSourceNode();
+ }
+
+ // Apply the style if we have something to which apply it.
+ if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
+ {
+ // Build the style element, based on the style object definition.
+ var styleNode = getElement( this, document );
+
+ var parent = styleRange.getCommonAncestor();
+
+ while ( styleNode && parent )
+ {
+ if ( parent.getName() == elementName )
+ {
+ for ( var attName in def.attribs )
+ {
+ if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
+ styleNode.removeAttribute( attName );
+ }
+
+ for ( var styleName in def.styles )
+ {
+ if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
+ styleNode.removeStyle( styleName );
+ }
+
+ if ( !styleNode.hasAttributes() )
+ {
+ styleNode = null;
+ break;
+ }
+ }
+
+ parent = parent.getParent();
+ }
+
+ if ( styleNode )
+ {
+ // Move the contents of the range to the style element.
+ styleRange.extractContents().appendTo( styleNode );
+
+ // Here we do some cleanup, removing all duplicated
+ // elements from the style element.
+ removeFromElement( this, styleNode );
+
+ // Insert it into the range position (it is collapsed after
+ // extractContents.
+ styleRange.insertNode( styleNode );
+
+ // Let's merge our new style with its neighbors, if possible.
+ mergeSiblings( styleNode );
+
+ // As the style system breaks text nodes constantly, let's normalize
+ // things for performance.
+ // With IE, some paragraphs get broken when calling normalize()
+ // repeatedly. Also, for IE, we must normalize body, not documentElement.
+ // IE is also known for having a "crash effect" with normalize().
+ // We should try to normalize with IE too in some way, somewhere.
+ if ( !CKEDITOR.env.ie )
+ styleNode.$.normalize();
+ }
+
+ // Style applied, let's release the range, so it gets
+ // re-initialization in the next loop.
+ styleRange = null;
+ }
+ }
+
+// this._FixBookmarkStart( startNode );
+
+ range.moveToBookmark( bookmark );
+ };
+
+ var applyBlockStyle = function( range )
+ {
+ };
+
+ // Removes a style from inside an element.
+ var removeFromElement = function( style, element )
+ {
+ var def = style._.definition;
+ var attribs = def.attributes;
+ var styles = def.styles;
+
+ var innerElements = element.getElementsByTag( style.element );
+
+ for ( var i = innerElements.count() ; --i >= 0 ; )
+ {
+ var innerElement = innerElements.getItem( i );
+
+ for ( var attName in attribs )
+ {
+ // The 'class' element value must match (#1318).
+ if ( attName == 'class' && innerElement.getAttribute( 'class' ) != attribs[ attName ] )
+ continue;
+
+ innerElement.removeAttribute( attName );
+ }
+
+ for ( var styleName in styles )
+ {
+ innerElement.removeStyle( styleName );
+ }
+
+ removeNoAttribsElement( innerElement );
+ }
+ };
+
+ // If the element has no more attributes, remove it.
+ var removeNoAttribsElement = function( element )
+ {
+ // If no more attributes remained in the element, remove it,
+ // leaving its children.
+ if ( !element.hasAttributes() )
+ {
+ // Removing elements may open points where merging is possible,
+ // so let's cache the first and last nodes for later checking.
+ var firstChild = element.getFirst();
+ var lastChild = element.getLast();
+
+ element.remove( true );
+
+ if ( firstChild )
+ {
+ // Check the cached nodes for merging.
+ mergeSiblings( firstChild );
+
+ if ( lastChild && !firstChild.equals( lastChild ) )
+ mergeSiblings( lastChild );
+ }
+ }
+ };
+
+ // Get the the collection used to compare the attributes defined in this
+ // style with attributes in an element. All information in it is lowercased.
+ // V2
+// var getAttribsForComparison = function( style )
+// {
+// // If we have already computed it, just return it.
+// var attribs = style._.attribsForComparison;
+// if ( attribs )
+// return attribs;
+
+// attribs = {};
+
+// var def = style._.definition;
+
+// // Loop through all defined attributes.
+// var styleAttribs = def.attributes;
+// if ( styleAttribs )
+// {
+// for ( var styleAtt in styleAttribs )
+// {
+// attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase();
+// }
+// }
+
+// // Includes the style definitions.
+// if ( this._GetStyleText().length > 0 )
+// {
+// attribs['style'] = this._GetStyleText().toLowerCase();
+// }
+
+// // Appends the "length" information to the object.
+// FCKTools.AppendLengthProperty( attribs, '_length' );
+
+// // Return it, saving it to the next request.
+// return ( this._GetAttribsForComparison_$ = attribs );
+// },
+
+ var mergeSiblings = function( element )
+ {
+ if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
+ return;
+
+ mergeElements( element, element.getNext(), true );
+ mergeElements( element, element.getPrevious() );
+ };
+
+ var mergeElements = function( element, sibling, isNext )
+ {
+ if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
+ {
+ var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
+
+ if ( hasBookmark )
+ sibling = isNext ? sibling.getNext() : sibling.getPrevious();
+
+ if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.getName() == element.getName() )
+ {
+ // Save the last child to be checked too, to merge things like
+ // =>
+ var innerSibling = isNext ? element.getLast() : element.getFirst();
+
+ if ( hasBookmark )
+ ( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
+
+ sibling.moveChildren( element, !isNext );
+ sibling.remove();
+
+ // Now check the last inner child (see two comments above).
+ if ( innerSibling )
+ mergeSiblings( innerSibling );
+ }
+ }
+ };
+
+ // Regex used to match all variables defined in an attribute or style
+ // value. The variable name is returned with $2.
+ var styleVariableAttNameRegex = /#\(\s*("|')(.+?)\1[^\)]*\s*\)/g;
+
+ var getElement = function( style, targetDocument )
+ {
+ var el = style._.element;
+
+ if ( el )
+ return el.clone();
+
+ var def = style._.definition;
+ var variables = style._.variables;
+
+ var elementName = style.element;
+ var attributes = def.attributes;
+ var styles = def.styles;
+
+ // The "*" element name will always be a span for this function.
+ if ( elementName == '*' )
+ elementName = 'span';
+
+ // Create the element.
+ el = new CKEDITOR.dom.element( elementName, targetDocument );
+
+ // Assign all defined attributes.
+ if ( attributes )
+ {
+ for ( var att in attributes )
+ {
+ var attValue = attributes[ att ];
+ if ( attValue && variables )
+ {
+ attValue = attValue.replace( styleVariableAttNameRegex, function()
+ {
+ // The second group in the regex is the variable name.
+ return variables[ arguments[2] ] || arguments[0];
+ });
+ }
+ el.setAttribute( att, attValue );
+ }
+ }
+
+ // Assign all defined styles.
+ if ( styles )
+ {
+ for ( var styleName in styles )
+ el.setStyle( styleName, styles[ styleName ] );
+
+ if ( variables )
+ {
+ attValue = el.getAttribute( 'style' ).replace( styleVariableAttNameRegex, function()
+ {
+ // The second group in the regex is the variable name.
+ return variables[ arguments[2] ] || arguments[0];
+ });
+ el.setAttribute( 'style', attValue );
+ }
+ }
+
+ // Save the created element. It will be reused on future calls.
+ return ( style._.element = el );
+ };
+})();
+
+CKEDITOR.styleCommand = function( styleDefinition )
+{
+ this.style = new CKEDITOR.style( styleDefinition );
+};
+
+CKEDITOR.styleCommand.prototype.exec = function( editor )
+{
+ editor.focus();
+
+ var doc = editor.document;
+
+ if ( doc )
+ this.style.apply( doc );
+
+ return !!doc;
+};