Ticket #2768: 2768_3.patch

File 2768_3.patch, 50.0 KB (added by Garry Yao, 15 years ago)
  • _source/core/tools.js

     
    310310                                return i;
    311311                }
    312312                return -1;
     313        },
     314        /**
     315         * Binds a function with an object as its this reference.
     316         *
     317         * @param {Function}
     318         *            func The function to be bounded.
     319         * @param {Object}
     320         *            obj This this reference to bind to the function.
     321         * @returns {Function} The bound function.
     322         */
     323        bind : function ( func, obj )
     324        {
     325                return function ()
     326                {
     327                        return func.apply( obj , arguments );
     328                }
    313329        }
    314330};
    315331
  • _source/core/editor.js

     
    379379                 */
    380380                execCommand : function( commandName, data )
    381381                {
    382                         var command = this.getCommand( commandName );
     382                        var command = this.getCommand( commandName ),
     383                        execResult,
     384                        commandEvent = {
     385                                name :commandName,
     386                                command :command
     387                        };
     388                       
    383389                        if ( command && command.state != CKEDITOR.TRISTATE_DISABLED )
    384                                 return command.exec( this, data );
     390                        {
     391                                exeResult = command.exec( this , data );
     392                                this.fire( 'afterCommandExec' , commandEvent );
     393                                return exeResult;
     394                        }
    385395
    386396                        // throw 'Unknown command name "' + commandName + '"';
    387397                        return false;
  • _source/core/dom/documentFragment.js

     
    77{
    88        this.$ = CKEDITOR.env.ie ? ownerDocument.$.createElement( 'div' ) : ownerDocument.$.createDocumentFragment();
    99};
    10 
     10/*
     11 * A DocumentFragment behaves like a conventional {@link CKEDITOR.dom.Node} with all of the same
     12 * methods, except that when a DocumentFragment is inserted into a Document (or
     13 * indeed any other Node that may take children) the children of the
     14 * DocumentFragment and not the DocumentFragment itself are inserted into the
     15 * Node.
     16 */
     17CKEDITOR.dom.documentFragment.prototype =
     18CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.node.prototype );
    1119(function()
    1220{
    1321        var elementPrototype = CKEDITOR.dom.element.prototype;
     
    1220{
    1321        var elementPrototype = CKEDITOR.dom.element.prototype;
    1422
    15         CKEDITOR.dom.documentFragment.prototype =
    16         {
     23        CKEDITOR.tools.extend( CKEDITOR.dom.documentFragment.prototype , {
    1724                type : CKEDITOR.NODE_DOCUMENT_FRAGMENT,
    1825
    1926                append : elementPrototype.append,
     
    4047                        else
    4148                                $parent.insertBefore( $, $node.nextSibling );
    4249                }
    43         };
     50        }, true );
     51       
    4452})();
  • _source/core/dom/range.js

     
    457457                        };
    458458                },
    459459
    460                 moveToBookmark : function( bookmark )
     460                moveToBookmark : function( bookmark, isPreserve )
    461461                {
     462                        var st = bookmark.startNode , ed = bookmark.endNode;
    462463                        // Set the range start at the bookmark start node position.
    463                         this.setStartBefore( bookmark.startNode );
     464                        this.setStartBefore( typeof st === 'string' ? ( st = this.document
     465                                        .getById( st ) ) : st );
    464466
    465467                        // Remove it, because it may interfere in the setEndBefore call.
    466                         bookmark.startNode.remove();
     468                        if(!isPreserve)
     469                                st.remove();
    467470
    468471                        // Set the range end at the bookmark end node position, or simply
    469472                        // collapse it if it is not available.
    470                         var endNode = bookmark.endNode;
    471                         if ( endNode )
     473                        if ( ed )
    472474                        {
    473                                 this.setEndBefore( endNode );
    474                                 endNode.remove();
     475                                this.setEndBefore( typeof ed === 'string' ?
     476                                                ( ed = this.document.getById( ed ) ) : ed );
     477                                if(!isPreserve)
     478                                        ed.remove();
    475479                        }
    476480                        else
    477481                                this.collapse( true );
     
    10021006
    10031007                        var startContainer = this.startContainer;
    10041008                        var startOffset = this.startOffset;
     1009                        var isDomFrag = false , firstChild , childCounts = 0;
     1010
     1011                        // Inserted node might be {@link CKEDITOR.dom.documentFragment}
     1012                        if ( node.type === CKEDITOR.NODE_DOCUMENT_FRAGMENT )
     1013                        {
     1014                                isDomFrag = true;
     1015                                firstChild = node.getChild( 0 );
     1016                                childCounts = node.getChildCount();
     1017                        }
    10051018
    10061019                        var nextNode = startContainer.getChild( startOffset );
    10071020
     
    10111024                                startContainer.append( node );
    10121025
    10131026                        // Check if we need to update the end boundary.
    1014                         if ( node.getParent().equals( this.endContainer ) )
     1027                        if ( isDomFrag && firstChild.getParent().equals( this.endContainer ) )
     1028                        {
     1029                                this.endOffset += childCounts;
     1030                                // Expand the range to embrace the new nodes.
     1031                                this.setStartBefore( firstChild );
     1032                        }
     1033                        else if ( node.getParent().equals( this.endContainer ) )
    10151034                                this.endOffset++;
    10161035
    10171036                        // Expand the range to embrace the new node.
  • _source/plugins/basicstyles/plugin.js

     
    1313                // duplications, let's use this tool function.
    1414                var addButtonCommand = function( buttonName, buttonLabel, commandName, styleDefiniton )
    1515                {
    16                         var style = new CKEDITOR.style( styleDefiniton );
     16                        var style = new CKEDITOR.style( editor, styleDefiniton );
    1717
    1818                        editor.attachStyleStateChange( style, function( state )
    1919                                {
  • _source/plugins/styles/plugin.js

     
    55
    66CKEDITOR.plugins.add( 'styles',
    77{
    8         requires : [ 'selection' ]
     8        requires : [ 'selection' ],
     9        beforeInit : function( editor, pluginPath )
     10        {}
    911});
     12(function(){
     13       
     14                var blockElements   = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
     15                var objectElements  = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
    1016
    11 /**
    12  * Registers a function to be called whenever a style changes its state in the
    13  * editing area. The current state is passed to the function. The possible
    14  * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.
    15  * @param {CKEDITOR.style} The style to be watched.
    16  * @param {Function} The function to be called when the style state changes.
    17  * @example
    18  * // Create a style object for the <b> element.
    19  * var style = new CKEDITOR.style( { element : 'b' } );
    20  * var editor = CKEDITOR.instances.editor1;
    21  * editor.attachStyleStateChange( style, function( state )
    22  *     {
    23  *         if ( state == CKEDITOR.TRISTATE_ON )
    24  *             alert( 'The current state for the B element is ON' );
    25  *         else
    26  *             alert( 'The current state for the B element is OFF' );
    27  *     });
    28  */
    29 CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback )
    30 {
    31         // Try to get the list of attached callbacks.
    32         var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
    33 
    34         // If it doesn't exist, it means this is the first call. So, let's create
    35         // all the structure to manage the style checks and the callback calls.
    36         if ( !styleStateChangeCallbacks )
    37         {
    38                 // Create the callbacks array.
    39                 styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
    40 
    41                 // Attach to the selectionChange event, so we can check the styles at
    42                 // that point.
    43                 this.on( 'selectionChange', function( ev )
     17                CKEDITOR.style = function( editor, styleDefinition )
     18                {
     19                        var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
     20       
     21                        this.type =
     22                                ( element == '#' || blockElements[ element ] ) ?
     23                                        CKEDITOR.STYLE_BLOCK
     24                                : objectElements[ element ] ?
     25                                        CKEDITOR.STYLE_OBJECT
     26                                :
     27                                        CKEDITOR.STYLE_INLINE;
     28       
     29                        this._ =
    4430                        {
    45                                 // Loop throw all registered callbacks.
    46                                 for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
     31                                editor: editor,
     32                                definition : styleDefinition
     33                        };
     34                };
     35       
     36                CKEDITOR.style.prototype =
     37                {
     38                        /**
     39                         * @param {CKEDITOR.dom.document} document The target document the style applies to.
     40                         * @param {Boolean} isReverse Whether it's a reverse apply (remove).
     41                         */
     42                        apply : function( isReverse )
     43                        {
     44                                var doc = this._.editor.document;
     45                                // Get all ranges from the selection.
     46                                var selection = doc.getSelection();
     47                                var ranges = selection.getRanges();
     48                                var styleFuncName = isReverse ? 'removeFromRange' : 'applyToRange';
     49                                // Apply the style to the ranges.
     50                                for (var i = 0; i < ranges.length; i++)
    4751                                {
    48                                         var callback = styleStateChangeCallbacks[ i ];
    49 
    50                                         // Check the current state for the style defined for that
    51                                         // callback.
    52                                         var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
    53 
    54                                         // If the state changed since the last check.
    55                                         if ( callback.state !== currentState )
     52                                        this[styleFuncName].call(this, ranges[i]);
     53                                }
     54                                // Select the ranges again.
     55                                selection.selectRanges(ranges);
     56                        },
     57                                       
     58                        reverse : function() {
     59                                this.apply( true );
     60                        },
     61       
     62                        applyToRange : function( range )
     63                        {
     64                                return ( this.applyToRange =
     65                                                        this.type == CKEDITOR.STYLE_INLINE ?
     66                                                                applyInlineStyle
     67                                                        : this.type == CKEDITOR.STYLE_BLOCK ?
     68                                                                applyBlockStyle
     69                                                        : null ).call( this, range );
     70                        },
     71                                       
     72                        removeFromRange : function (range) {
     73                       
     74                                        return (this.type == CKEDITOR.STYLE_INLINE ? removeInlineStyle :
     75                                        this.type == CKEDITOR.STYLE_BLOCK ? removeBlockStyle :
     76                                        null).call(this, range);
     77                        },
     78                                       
     79                        /**
     80                         * Remove all related attributes, styles on the element, or even intrinsic semantics(the element itself) which related with this style
     81                         * @param {CKEditor.dom.element} element The target element
     82                         */
     83                        removeFromElement: function(element)
     84                        {
     85                                if(element.$.nodeType !== 1||element.getName() !== this.element)
     86                                        return;
     87                                var def = this._.definition;
     88                                var attribs = def.attributes;
     89                                var styles = def.styles;
     90                               
     91                                for (var attName in attribs)
     92                                {
     93                                        // The 'class' element value must match (#1318).
     94                                        if (attName == 'class' && element.getAttribute('class') != attribs[attName])
     95                                                continue;
     96                                       
     97                                        element.removeAttribute(attName);
     98                                }
     99                               
     100                                for (var styleName in styles)
     101                                {
     102                                        element.removeStyle(styleName);
     103                                }
     104                               
     105                                removeNoAttribsElement(element);
     106                        },
     107                                       
     108                        /**
     109                         * Remove related style from all the childs of this element
     110                         * @param {CKEditor.dom.element} element The target element
     111                         * @see CKEditor.style::removeFromElement
     112                         */
     113                        removeFromAllChilds : function( element )
     114                        {
     115                                        var innerElements = element.getElementsByTag( this.element );
     116                                        for ( var i = innerElements.count() ; --i >= 0 ; )
    56117                                        {
    57                                                 // Call the callback function, passing the current
    58                                                 // state to it.
    59                                                 callback.fn.call( this, currentState );
    60 
    61                                                 // Save the current state, so it can be compared next
    62                                                 // time.
    63                                                 callback.state !== currentState;
     118                                                var innerElement = innerElements.getItem( i );
     119                                                this.removeFromElement( innerElements );
    64120                                        }
     121                        },
     122       
     123                        /**
     124                         * Get the style state inside an element path. Returns "true" if the
     125                         * element is active in the path.
     126                         */
     127                        checkActive : function( elementPath )
     128                        {
     129                                switch ( this.type )
     130                                {
     131                                        case CKEDITOR.STYLE_BLOCK :
     132                                       
     133                                                return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
     134       
     135                                        case CKEDITOR.STYLE_INLINE :
     136       
     137                                                var elements = elementPath.elements;
     138       
     139                                                for ( var i = 0, element ; i < elements.length ; i++ )
     140                                                {
     141                                                        element = elements[i];
     142       
     143                                                        if ( element == elementPath.block || element == elementPath.blockLimit )
     144                                                                continue;
     145       
     146                                                        if ( this.checkElementRemovable( element, true ) )
     147                                                                return true;
     148                                                }
    65149                                }
    66                         });
    67         }
    68 
    69         // Save the callback info, so it can be checked on the next occurence of
    70         // selectionChange.
    71         styleStateChangeCallbacks.push( { style : style, fn : callback } );
    72 };
    73 
    74 CKEDITOR.STYLE_BLOCK = 1;
    75 CKEDITOR.STYLE_INLINE = 2;
    76 CKEDITOR.STYLE_OBJECT = 3;
    77 
    78 (function()
    79 {
    80         var blockElements       = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
    81         var objectElements      = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
    82 
    83         CKEDITOR.style = function( styleDefinition )
    84         {
    85                 var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
    86 
    87                 this.type =
    88                         ( element == '#' || blockElements[ element ] ) ?
    89                                 CKEDITOR.STYLE_BLOCK
    90                         : objectElements[ element ] ?
    91                                 CKEDITOR.STYLE_OBJECT
    92                         :
    93                                 CKEDITOR.STYLE_INLINE;
    94 
    95                 this._ =
    96                 {
    97                         definition : styleDefinition
    98                 };
    99         };
    100 
    101         CKEDITOR.style.prototype =
    102         {
    103                 apply : function( document )
    104                 {
    105                         // Get all ranges from the selection.
    106                         var selection = document.getSelection();
    107                         var ranges = selection.getRanges();
    108 
    109                         // Apply the style to the ranges.
    110                         for ( var i = 0 ; i < ranges.length ; i++ )
    111                                 this.applyToRange( ranges[ i ] );
    112 
    113                         // Select the ranges again.
    114                         selection.selectRanges( ranges );
    115                 },
    116 
    117                 applyToRange : function( range )
    118                 {
    119                         return ( this.applyToRange =
    120                                                 this.type == CKEDITOR.STYLE_INLINE ?
    121                                                         applyInlineStyle
    122                                                 : this.type == CKEDITOR.STYLE_BLOCK ?
    123                                                         applyBlockStyle
    124                                                 : null ).call( this, range );
    125                 },
    126 
    127                 /**
    128                  * Get the style state inside an element path. Returns "true" if the
    129                  * element is active in the path.
    130                  */
    131                 checkActive : function( elementPath )
    132                 {
    133                         switch ( this.type )
     150                                return false;
     151                        },
     152       
     153                        /**
     154                         * @param {CKEDITOR.dom.element} element The target element to check.
     155                         * @param {Boolean} fullMatch Whether require the attribute and styles are fully matched.
     156                         */
     157                        checkElementRemovable : function( element, fullMatch )
    134158                        {
    135                                 case CKEDITOR.STYLE_BLOCK :
    136                                         return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
    137 
    138                                 case CKEDITOR.STYLE_INLINE :
    139 
    140                                         var elements = elementPath.elements;
    141 
    142                                         for ( var i = 0, element ; i < elements.length ; i++ )
     159                                if ( !element || element.getName() != this.element )
     160                                        return false ;
     161       
     162                                var def = this._.definition;
     163                                var attribs = def.attributes;
     164                                var styles = def.styles;
     165       
     166                                // If no attributes are defined in the element.
     167                                if ( !fullMatch && !element.hasAttributes() )
     168                                        return true ;
     169       
     170                                for ( var attName in attribs )
     171                                {
     172                                        if ( element.getAttribute( attName ) == attribs[ attName ] )
    143173                                        {
    144                                                 element = elements[i];
    145 
    146                                                 if ( element == elementPath.block || element == elementPath.blockLimit )
    147                                                         continue;
    148 
    149                                                 if ( this.checkElementRemovable( element, true ) )
     174                                                if ( !fullMatch )
    150175                                                        return true;
    151176                                        }
    152                         }
    153                         return false;
    154                 },
    155 
    156                 // Checks if an element, or any of its attributes, is removable by the
    157                 // current style definition.
    158                 checkElementRemovable : function( element, fullMatch )
    159                 {
    160                         if ( !element || element.getName() != this.element )
    161                                 return false ;
    162 
    163                         var def = this._.definition;
    164                         var attribs = def.attributes;
    165                         var styles = def.styles;
    166 
    167                         // If no attributes are defined in the element.
    168                         if ( !fullMatch && !element.hasAttributes() )
    169                                 return true ;
    170 
    171                         for ( var attName in attribs )
    172                         {
    173                                 if ( element.getAttribute( attName ) == attribs[ attName ] )
    174                                 {
    175                                         if ( !fullMatch )
    176                                                 return true;
     177                                        else if ( fullMatch )
     178                                                return false;
    177179                                }
    178                                 else if ( fullMatch )
    179                                         return false;
     180       
     181                                return true;
     182                        },
     183       
     184                        /**
     185                         * Sets the value of a variable attribute or style, to be used when
     186                         * appliying the style. This function must be called before using any
     187                         * other function in this object.
     188                         */
     189                        setVariable : function( name, value )
     190                        {
     191                                var variables = this._.variables || ( this._variables = {} );
     192                                variables[ name ] = value;
    180193                        }
    181 
    182                         return true;
    183                 },
    184 
    185                 /**
    186                  * Sets the value of a variable attribute or style, to be used when
    187                  * appliying the style. This function must be called before using any
    188                  * other function in this object.
    189                  */
    190                 setVariable : function( name, value )
    191                 {
    192                         var variables = this._.variables || ( this._variables = {} );
    193                         variables[ name ] = value;
    194                 }
    195         };
    196 
    197         var applyInlineStyle = function( range )
    198         {
    199                 var document = range.document;
    200 
    201                 if ( range.collapsed )
    202                 {
    203                         // Create the element to be inserted in the DOM.
    204                         var collapsedElement = getElement( this, document );
    205 
    206                         // Insert the empty element into the DOM at the range position.
    207                         range.insertNode( collapsedElement );
    208 
    209                         // Place the selection right inside the empty element.
    210                         range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
    211 
    212                         return;
    213                 }
    214 
    215                 var elementName = this.element;
    216                 var def = this._.definition;
    217                 var isUnknownElement;
    218 
    219                 // Get the DTD definition for the element. Defaults to "span".
    220                 var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
    221 
    222                 // Bookmark the range so we can re-select it after processing.
    223                 var bookmark = range.createBookmark();
    224 
    225                 // Expand the range.
    226                 range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
    227                 range.trim();
    228 
    229                 // Get the first node to be processed and the last, which concludes the
    230                 // processing.
    231                 var firstNode = range.startContainer.getChild( range.startOffset ) || range.startContainer.getNextSourceNode();
    232                 var lastNode = range.endContainer.getChild( range.endOffset ) || ( range.endOffset ? range.endContainer.getNextSourceNode() : range.endContainer );
    233 
    234                 var currentNode = firstNode;
    235 
    236                 var styleRange;
    237 
    238                 // Indicates that that some useful inline content has been found, so
    239                 // the style should be applied.
    240                 var hasContents;
    241 
    242                 while ( currentNode )
     194                };
     195       
     196                var applyInlineStyle = function( range )
    243197                {
    244                         var applyStyle = false;
    245 
    246                         if ( currentNode.equals( lastNode ) )
     198                        var document = range.document;
     199       
     200                        if ( range.collapsed )
    247201                        {
    248                                 currentNode = null;
    249                                 applyStyle = true;
     202                                // Create the element to be inserted in the DOM.
     203                                var collapsedElement = getElement( this, document );
     204       
     205                                // Insert the empty element into the DOM at the range position.
     206                                range.insertNode( collapsedElement );
     207       
     208                                // Place the selection right inside the empty element.
     209                                range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
     210       
     211                                return;
    250212                        }
    251                         else
     213       
     214                        var elementName = this.element;
     215                        var def = this._.definition;
     216                        var isUnknownElement;
     217       
     218                        // Get the DTD definition for the element. Defaults to "span".
     219                        var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
     220       
     221                        // Bookmark the range so we can re-select it after processing.
     222                        var bookmark = range.createBookmark();
     223       
     224                        // Expand the range.
     225                        range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
     226                        range.trim();
     227       
     228                        // Get the first node to be processed and the last, which concludes the
     229                        // processing.
     230                        var firstNode = range.startContainer.getChild( range.startOffset ) || range.startContainer.getNextSourceNode();
     231                        var lastNode = range.endContainer.getChild( range.endOffset ) || ( range.endOffset ? range.endContainer.getNextSourceNode() : range.endContainer );
     232       
     233                        var currentNode = firstNode;
     234       
     235                        var styleRange;
     236       
     237                        // Indicates that that some useful inline content has been found, so
     238                        // the style should be applied.
     239                        var hasContents;
     240       
     241                        while ( currentNode )
    252242                        {
    253                                 var nodeType = currentNode.type;
    254                                 var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
    255 
    256                                 if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
     243                                var applyStyle = false;
     244       
     245                                if ( currentNode.equals( lastNode ) )
    257246                                {
    258                                         currentNode = currentNode.getNextSourceNode( true );
    259                                         continue;
     247                                        currentNode = null;
     248                                        applyStyle = true;
    260249                                }
    261 
    262                                 // Check if the current node can be a child of the style element.
    263                                 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 ) ) )
     250                                else
    264251                                {
    265                                         var currentParent = currentNode.getParent();
    266 
    267                                         // Check if the style element can be a child of the current
    268                                         // node parent or if the element is not defined in the DTD.
    269                                         if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
     252                                        var nodeType = currentNode.type;
     253                                        var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
     254       
     255                                        if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
     256                                        {
     257                                                currentNode = currentNode.getNextSourceNode( true );
     258                                                continue;
     259                                        }
     260       
     261                                        // Check if the current node can be a child of the style element.
     262                                        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 ) ) )
    270263                                        {
    271                                                 // This node will be part of our range, so if it has not
    272                                                 // been started, place its start right before the node.
    273                                                 // In the case of an element node, it will be included
    274                                                 // only if it is entirely inside the range.
    275                                                 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 ) ) )
     264                                                var currentParent = currentNode.getParent();
     265       
     266                                                // Check if the style element can be a child of the current
     267                                                // node parent or if the element is not defined in the DTD.
     268                                                if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
    276269                                                {
    277                                                         styleRange = new CKEDITOR.dom.range( document );
    278                                                         styleRange.setStartBefore( currentNode );
    279                                                 }
    280 
    281                                                 // Non element nodes, or empty elements can be added
    282                                                 // completely to the range.
    283                                                 if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() && currentNode.$.offsetWidth ) )
    284                                                 {
    285                                                         var includedNode = currentNode;
    286                                                         var parentNode;
    287 
    288                                                         // This node is about to be included completelly, but,
    289                                                         // if this is the last node in its parent, we must also
    290                                                         // check if the parent itself can be added completelly
    291                                                         // to the range.
    292                                                         while ( !includedNode.$.nextSibling
    293                                                                 && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
    294                                                                 && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
     270                                                        // This node will be part of our range, so if it has not
     271                                                        // been started, place its start right before the node.
     272                                                        // In the case of an element node, it will be included
     273                                                        // only if it is entirely inside the range.
     274                                                        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 ) ) )
    295275                                                        {
    296                                                                 includedNode = parentNode;
     276                                                                styleRange = new CKEDITOR.dom.range( document );
     277                                                                styleRange.setStartBefore( currentNode );
    297278                                                        }
    298 
    299                                                         styleRange.setEndAfter( includedNode );
    300 
    301                                                         // If the included node still is the last node in its
    302                                                         // parent, it means that the parent can't be included
    303                                                         // in this style DTD, so apply the style immediately.
    304                                                         if ( !includedNode.$.nextSibling )
    305                                                                 applyStyle = true;
    306 
    307                                                         if ( !hasContents )
    308                                                                 hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
     279       
     280                                                        // Non element nodes, or empty elements can be added
     281                                                        // completely to the range.
     282                                                        if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() && currentNode.$.offsetWidth ) )
     283                                                        {
     284                                                                var includedNode = currentNode;
     285                                                                var parentNode;
     286       
     287                                                                // This node is about to be included completelly, but,
     288                                                                // if this is the last node in its parent, we must also
     289                                                                // check if the parent itself can be added completelly
     290                                                                // to the range.
     291                                                                while ( !includedNode.$.nextSibling
     292                                                                        && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
     293                                                                        && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
     294                                                                {
     295                                                                        includedNode = parentNode;
     296                                                                }
     297       
     298                                                                styleRange.setEndAfter( includedNode );
     299       
     300                                                                // If the included node still is the last node in its
     301                                                                // parent, it means that the parent can't be included
     302                                                                // in this style DTD, so apply the style immediately.
     303                                                                if ( !includedNode.$.nextSibling )
     304                                                                        applyStyle = true;
     305       
     306                                                                if ( !hasContents )
     307                                                                        hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
     308                                                        }
    309309                                                }
     310                                                else
     311                                                        applyStyle = true;
    310312                                        }
    311313                                        else
    312314                                                applyStyle = true;
     315       
     316                                        // Get the next node to be processed.
     317                                        currentNode = currentNode.getNextSourceNode();
    313318                                }
    314                                 else
    315                                         applyStyle = true;
    316 
    317                                 // Get the next node to be processed.
    318                                 currentNode = currentNode.getNextSourceNode();
    319                         }
    320 
    321                         // Apply the style if we have something to which apply it.
    322                         if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
    323                         {
    324                                 // Build the style element, based on the style object definition.
    325                                 var styleNode = getElement( this, document );
    326 
    327                                 var parent = styleRange.getCommonAncestor();
    328 
    329                                 while ( styleNode && parent )
     319       
     320                                // Apply the style if we have something to which apply it.
     321                                if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
    330322                                {
    331                                         if ( parent.getName() == elementName )
     323                                        // Build the style element, based on the style object definition.
     324                                        var styleNode = getElement( this, document );
     325       
     326                                        var parent = styleRange.getCommonAncestor();
     327       
     328                                        while ( styleNode && parent )
    332329                                        {
    333                                                 for ( var attName in def.attribs )
     330                                                if ( parent.getName() == elementName )
    334331                                                {
    335                                                         if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
    336                                                                 styleNode.removeAttribute( attName );
    337                                                 }
    338 
    339                                                 for ( var styleName in def.styles )
    340                                                 {
    341                                                         if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
    342                                                                 styleNode.removeStyle( styleName );
    343                                                 }
    344 
    345                                                 if ( !styleNode.hasAttributes() )
    346                                                 {
    347                                                         styleNode = null;
    348                                                         break;
     332                                                        for ( var attName in def.attribs )
     333                                                        {
     334                                                                if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
     335                                                                        styleNode.removeAttribute( attName );
     336                                                        }
     337       
     338                                                        for ( var styleName in def.styles )
     339                                                        {
     340                                                                if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
     341                                                                        styleNode.removeStyle( styleName );
     342                                                        }
     343       
     344                                                        if ( !styleNode.hasAttributes() )
     345                                                        {
     346                                                                styleNode = null;
     347                                                                break;
     348                                                        }
    349349                                                }
     350       
     351                                                parent = parent.getParent();
    350352                                        }
    351 
    352                                         parent = parent.getParent();
     353       
     354                                        if ( styleNode )
     355                                        {
     356                                                // Move the contents of the range to the style element.
     357                                                styleRange.extractContents().appendTo( styleNode );
     358       
     359                                                // Here we do some cleanup, removing all duplicated
     360                                                // elements from the style element.
     361                                                                                        this.removeFromAllChilds( styleNode );
     362       
     363                                                // Insert it into the range position (it is collapsed after
     364                                                // extractContents.
     365                                                styleRange.insertNode( styleNode );
     366       
     367                                                // Let's merge our new style with its neighbors, if possible.
     368                                                mergeSiblings( styleNode );
     369       
     370                                                // As the style system breaks text nodes constantly, let's normalize
     371                                                // things for performance.
     372                                                // With IE, some paragraphs get broken when calling normalize()
     373                                                // repeatedly. Also, for IE, we must normalize body, not documentElement.
     374                                                // IE is also known for having a "crash effect" with normalize().
     375                                                // We should try to normalize with IE too in some way, somewhere.
     376                                                if ( !CKEDITOR.env.ie )
     377                                                        styleNode.$.normalize();
     378                                        }
     379       
     380                                        // Style applied, let's release the range, so it gets
     381                                        // re-initialization in the next loop.
     382                                        styleRange = null;
    353383                                }
    354 
    355                                 if ( styleNode )
     384                        }
     385       
     386        //              this._FixBookmarkStart( startNode );
     387       
     388                        range.moveToBookmark( bookmark );
     389                };
     390                       
     391                /**
     392                 * Remove the style from the specified {@param range}.
     393                 * @param {CKEDITOR.dom.range} range    The range to be processed.
     394                 */
     395                function removeInlineStyle(range) {
     396               
     397                        var doc = range.document;
     398                        var self = this, //bookmark start and end boundaries
     399                        bookmark, startNode, endNode, walker, //the topmost parent node which conflicts with this style removing
     400                        matchedTopParent;
     401       
     402                        var isCollapsed = range.collapsed, // A placeholder for cheat with collapsed range
     403                        marker;
     404               
     405                       
     406                        // Expand the range, if inside inline element boundaries.
     407                        if ( !isCollapsed )
     408                                range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
     409               
     410                        //Cheat by insert a placeholder node which make the range no longer collapsed
     411                        else         
     412                        {
     413                                marker = doc.createText( '&nbsp;' );
     414                                range.insertNode( marker );
     415                                range.setStartBefore( marker );
     416                                range.setEndAfter( marker );
     417                        }
     418       
     419                        // Bookmark the range, bookmark nodes are used to establish boundaries.
     420                        bookmark = range.createBookmark(),
     421                        startNode   = bookmark.startNode,
     422                        endNode = bookmark.endNode;
     423       
     424                        if ( matchedTopParent = checkPath( startNode , CKEDITOR.tools.bind(
     425                                                        this.checkElementRemovable , this ) ) )
     426                                branch( startNode , matchedTopParent , true );
     427                        if ( matchedTopParent = checkPath( endNode , CKEDITOR.tools.bind(
     428                                                        this.checkElementRemovable , this ) ) )
     429                                branch( endNode , matchedTopParent , true );
     430               
     431                        walker = new CKEDITOR.dom.domWalker( startNode );
     432                        walker.forward( function ( walkerEvt )
     433                        {
     434                                var currentNode = walkerEvt.data.from;
     435                                if ( currentNode.equals( startNode ) )
     436                                                return;
     437                                else if ( currentNode.equals( endNode ) )
     438                                                this.stop();
     439                                else
     440                                        {
     441                                                self.removeFromElement( currentNode );
     442                                        }       
     443                        } );
     444                       
     445                        if ( isCollapsed ) // remove the marker node
     446                        {
     447                                marker.remove();
     448                        }
     449                        else
     450                        {
     451                                if ( !CKEDITOR.env.ie ) // normalize text nodes for non-IE
    356452                                {
    357                                         // Move the contents of the range to the style element.
    358                                         styleRange.extractContents().appendTo( styleNode );
    359 
    360                                         // Here we do some cleanup, removing all duplicated
    361                                         // elements from the style element.
    362                                         removeFromElement( this, styleNode );
    363 
    364                                         // Insert it into the range position (it is collapsed after
    365                                         // extractContents.
    366                                         styleRange.insertNode( styleNode );
    367 
    368                                         // Let's merge our new style with its neighbors, if possible.
    369                                         mergeSiblings( styleNode );
    370 
    371                                         // As the style system breaks text nodes constantly, let's normalize
    372                                         // things for performance.
    373                                         // With IE, some paragraphs get broken when calling normalize()
    374                                         // repeatedly. Also, for IE, we must normalize body, not documentElement.
    375                                         // IE is also known for having a "crash effect" with normalize().
    376                                         // We should try to normalize with IE too in some way, somewhere.
    377                                         if ( !CKEDITOR.env.ie )
    378                                                 styleNode.$.normalize();
     453                                        var frag;
     454                                        range.moveToBookmark( bookmark , true );
     455                                        frag = range.extractContents();
     456                                       
     457                                        var boundaryNode = endNode.getPrevious();
     458                                       
     459                                        frag.$.normalize();
     460                                        range.insertNode( frag );
     461                                        //Merge nodes which splited by the extract operation if possible.
     462                                        mergeSiblings(boundaryNode);
    379463                                }
    380 
    381                                 // Style applied, let's release the range, so it gets
    382                                 // re-initialization in the next loop.
    383                                 styleRange = null;
    384464                        }
     465                               
     466                        //TODO: Anchor range boundaries inside element if possible
     467                       
     468                        range.moveToBookmark(bookmark);
     469                };
     470               
     471                var applyBlockStyle = function( range )
     472                {
     473                };
     474                       
     475                /**
     476                 * Create a new branch of the dom tree which rooted by {@param parentNode} and start from the {@param boundaryNode}, after then the {@param boundaryNode} would be placed between these two branches.
     477                 * For example in the following dom tree, if we specified '<span/>' as the boundaryNode,
     478                 * <pre>
     479                 *      <b>This <i>is some<span /> sample</i> test text</b>
     480                 *      </pre>
     481                 *The dom tree will end up with:
     482                 *<pre>     
     483                 *      <b>This <i>is some</i><span /><i> sample</i> test text</b>          <!-- (If parent = <i>) -->
     484                 *      <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>   <!-- (If parent = <b>) -->
     485                 *<pre>     
     486                 * @param {CKEDITOR.dom.node} boundaryNode The left-most node which used as boundary of this branch.
     487                 * @param {CKEDITOR.dom.node} parentNode The first node which will be the root of this branch.
     488                 * @see FCKDomTools.BreakParent The v2 similiar method.
     489                 */
     490                function branch ( boundaryNode, parentNode )
     491                {
     492                        var doc = new CKEDITOR.dom.document(( boundaryNode || parentNode ).$.ownerDocument);
     493                        var range = new CKEDITOR.dom.range( doc );
     494       
     495                        // We'll be extracting part of this boundaryNode, so let's use our
     496                        // range to get the correct piece.
     497                        range.setStartAfter( boundaryNode );
     498                        range.setEndAfter( parentNode );
     499       
     500                        // Extract it, range should be collapsed after the first branch
     501                        var docFrag = range.extractContents();
     502       
     503                        // Place the boundary node right after first branch
     504                        range.insertNode( boundaryNode.remove() );
     505       
     506                        // Insert the second branch in following
     507                        docFrag.insertAfterNode( boundaryNode );
    385508                }
    386 
    387 //              this._FixBookmarkStart( startNode );
    388 
    389                 range.moveToBookmark( bookmark );
    390         };
    391 
    392         var applyBlockStyle = function( range )
    393         {
    394         };
    395 
    396         // Removes a style from inside an element.
    397         var removeFromElement = function( style, element )
    398         {
    399                 var def = style._.definition;
    400                 var attribs = def.attributes;
    401                 var styles = def.styles;
    402 
    403                 var innerElements = element.getElementsByTag( style.element );
    404 
    405                 for ( var i = innerElements.count() ; --i >= 0 ; )
     509               
     510               
     511                /**
     512                 * Reversely seaching on the element path which started by {@param pathStartNode}, return the first path element which satisfy {@param testFunction}.
     513                 * @param {CKEditor.dom.node} pathStartNode
     514                 * @param {Functoin} testFunction The function to test whether the element is the root of branch.
     515                 * @param {Boolean} skipBlock Whether ignore all the block level elements included in this path.
     516                 */
     517                function checkPath(pathStartNode, testFunction, skipBlock)
    406518                {
    407                         var innerElement = innerElements.getItem( i );
    408 
    409                         for ( var attName in attribs )
     519                        // Let's start checking the start boundary.
     520                        var path = new CKEDITOR.dom.elementPath( pathStartNode ), currentElements = path.elements;
     521                        var currentElement;
     522                        var i = skipBlock ? CKEDITOR.tools.indexOf( currentElements , path.block ) - 1
     523                                                        : currentElements.length - 1;
     524                       
     525                        for (; i > 0 ; i--) // skip the beginning node
    410526                        {
    411                                 // The 'class' element value must match (#1318).
    412                                 if ( attName == 'class' && innerElement.getAttribute( 'class' ) != attribs[ attName ] )
    413                                         continue;
    414 
    415                                 innerElement.removeAttribute( attName );
     527                                currentElement = currentElements[ i ];
     528                               
     529                                if (testFunction( currentElement ))
     530                                {
     531                                        return currentElement;
     532                                }
    416533                        }
    417 
    418                         for ( var styleName in styles )
     534                };
     535               
     536                /**
     537                 * If no more attributes remained in the element, remove it, but leaving its
     538                 * children.
     539                 *
     540                 * @param {CKEDITOR.dom.element}
     541                 *            The target element to remove.
     542                 */
     543                function removeNoAttribsElement( element )
     544                {
     545                        // If no more attributes remained in the element, remove it,
     546                        // leaving its children.
     547                        if ( !element.hasAttributes() )
    419548                        {
    420                                 innerElement.removeStyle( styleName );
     549                                //Merge sibling on remove element if possible
     550                                var parent = element.getParent(),
     551                                                previous = element.getPrevious();
     552                                               
     553                                element.remove( true );
     554                                mergeSiblings( previous );
     555                                mergeSiblings( parent );
    421556                        }
    422 
    423                         removeNoAttribsElement( innerElement );
    424                 }
    425         };
    426 
    427         // If the element has no more attributes, remove it.
    428         var removeNoAttribsElement = function( element )
    429         {
    430                 // If no more attributes remained in the element, remove it,
    431                 // leaving its children.
    432                 if ( !element.hasAttributes() )
     557                };
     558       
     559                // Get the the collection used to compare the attributes defined in this
     560                // style with attributes in an element. All information in it is lowercased.
     561                // V2
     562        //      var getAttribsForComparison = function( style )
     563        //      {
     564        //              // If we have already computed it, just return it.
     565        //              var attribs = style._.attribsForComparison;
     566        //              if ( attribs )
     567        //                      return attribs;
     568       
     569        //              attribs = {};
     570       
     571        //              var def = style._.definition;
     572       
     573        //              // Loop through all defined attributes.
     574        //              var styleAttribs = def.attributes;
     575        //              if ( styleAttribs )
     576        //              {
     577        //                      for ( var styleAtt in styleAttribs )
     578        //                      {
     579        //                              attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase();
     580        //                      }
     581        //              }
     582       
     583        //              // Includes the style definitions.
     584        //              if ( this._GetStyleText().length > 0 )
     585        //              {
     586        //                      attribs['style'] = this._GetStyleText().toLowerCase();
     587        //              }
     588       
     589        //              // Appends the "length" information to the object.
     590        //              FCKTools.AppendLengthProperty( attribs, '_length' );
     591       
     592        //              // Return it, saving it to the next request.
     593        //              return ( this._GetAttribsForComparison_$ = attribs );
     594        //      },
     595       
     596                var mergeSiblings = function( element )
     597                {
     598                        if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
     599                                return;
     600       
     601                        mergeElements( element, element.getNext(), true );
     602                        mergeElements( element, element.getPrevious() );
     603                };
     604       
     605                var mergeElements = function( element, sibling, isNext )
    433606                {
    434                         // Removing elements may open points where merging is possible,
    435                         // so let's cache the first and last nodes for later checking.
    436                         var firstChild  = element.getFirst();
    437                         var lastChild   = element.getLast();
    438 
    439                         element.remove( true );
    440 
    441                         if ( firstChild )
     607                        if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
    442608                        {
    443                                 // Check the cached nodes for merging.
    444                                 mergeSiblings( firstChild );
    445 
    446                                 if ( lastChild && !firstChild.equals( lastChild ) )
    447                                         mergeSiblings( lastChild );
     609                                var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
     610       
     611                                if ( hasBookmark )
     612                                        sibling = isNext ? sibling.getNext() : sibling.getPrevious();
     613       
     614                                if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.getName() == element.getName() )
     615                                {
     616                                        // Save the last child to be checked too, to merge things like
     617                                        // <b><i></i></b><b><i></i></b> => <b><i></i></b>
     618                                        var innerSibling = isNext ? element.getLast() : element.getFirst();
     619       
     620                                        if ( hasBookmark )
     621                                                ( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
     622       
     623                                        sibling.moveChildren( element, !isNext );
     624                                        sibling.remove();
     625       
     626                                        // Now check the last inner child (see two comments above).
     627                                        if ( innerSibling )
     628                                                mergeSiblings( innerSibling );
     629                                }
    448630                        }
    449                 }
    450         };
    451 
    452         // Get the the collection used to compare the attributes defined in this
    453         // style with attributes in an element. All information in it is lowercased.
    454         // V2
    455 //      var getAttribsForComparison = function( style )
    456 //      {
    457 //              // If we have already computed it, just return it.
    458 //              var attribs = style._.attribsForComparison;
    459 //              if ( attribs )
    460 //                      return attribs;
    461 
    462 //              attribs = {};
    463 
    464 //              var def = style._.definition;
    465 
    466 //              // Loop through all defined attributes.
    467 //              var styleAttribs = def.attributes;
    468 //              if ( styleAttribs )
    469 //              {
    470 //                      for ( var styleAtt in styleAttribs )
    471 //                      {
    472 //                              attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase();
    473 //                      }
    474 //              }
    475 
    476 //              // Includes the style definitions.
    477 //              if ( this._GetStyleText().length > 0 )
    478 //              {
    479 //                      attribs['style'] = this._GetStyleText().toLowerCase();
    480 //              }
    481 
    482 //              // Appends the "length" information to the object.
    483 //              FCKTools.AppendLengthProperty( attribs, '_length' );
    484 
    485 //              // Return it, saving it to the next request.
    486 //              return ( this._GetAttribsForComparison_$ = attribs );
    487 //      },
    488 
    489         var mergeSiblings = function( element )
    490         {
    491                 if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
    492                         return;
    493 
    494                 mergeElements( element, element.getNext(), true );
    495                 mergeElements( element, element.getPrevious() );
    496         };
    497 
    498         var mergeElements = function( element, sibling, isNext )
    499         {
    500                 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
     631                };
     632       
     633                // Regex used to match all variables defined in an attribute or style
     634                // value. The variable name is returned with $2.
     635                var styleVariableAttNameRegex = /#\(\s*("|')(.+?)\1[^\)]*\s*\)/g;
     636       
     637                var getElement = function( style, targetDocument )
    501638                {
    502                         var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
    503 
    504                         if ( hasBookmark )
    505                                 sibling = isNext ? sibling.getNext() : sibling.getPrevious();
    506 
    507                         if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.getName() == element.getName() )
     639                        var el = style._.element;
     640       
     641                        if ( el )
     642                                return el.clone();
     643       
     644                        var def = style._.definition;
     645                        var variables = style._.variables;
     646       
     647                        var elementName = style.element;
     648                        var attributes = def.attributes;
     649                        var styles = def.styles;
     650       
     651                        // The "*" element name will always be a span for this function.
     652                        if ( elementName == '*' )
     653                                elementName = 'span';
     654       
     655                        // Create the element.
     656                        el = new CKEDITOR.dom.element( elementName, targetDocument );
     657       
     658                        // Assign all defined attributes.
     659                        if ( attributes )
    508660                        {
    509                                 // Save the last child to be checked too, to merge things like
    510                                 // <b><i></i></b><b><i></i></b> => <b><i></i></b>
    511                                 var innerSibling = isNext ? element.getLast() : element.getFirst();
    512 
    513                                 if ( hasBookmark )
    514                                         ( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
    515 
    516                                 sibling.moveChildren( element, !isNext );
    517                                 sibling.remove();
    518 
    519                                 // Now check the last inner child (see two comments above).
    520                                 if ( innerSibling )
    521                                         mergeSiblings( innerSibling );
     661                                for ( var att in attributes )
     662                                {
     663                                        var attValue = attributes[ att ];
     664                                        if ( attValue && variables )
     665                                        {
     666                                                attValue = attValue.replace( styleVariableAttNameRegex, function()
     667                                                        {
     668                                                                // The second group in the regex is the variable name.
     669                                                                return variables[ arguments[2] ] || arguments[0];
     670                                                        });
     671                                        }
     672                                        el.setAttribute( att, attValue );
     673                                }
    522674                        }
    523                 }
    524         };
    525 
    526         // Regex used to match all variables defined in an attribute or style
    527         // value. The variable name is returned with $2.
    528         var styleVariableAttNameRegex = /#\(\s*("|')(.+?)\1[^\)]*\s*\)/g;
    529 
    530         var getElement = function( style, targetDocument )
    531         {
    532                 var el = style._.element;
    533 
    534                 if ( el )
    535                         return el.clone();
    536 
    537                 var def = style._.definition;
    538                 var variables = style._.variables;
    539 
    540                 var elementName = style.element;
    541                 var attributes = def.attributes;
    542                 var styles = def.styles;
    543 
    544                 // The "*" element name will always be a span for this function.
    545                 if ( elementName == '*' )
    546                         elementName = 'span';
    547 
    548                 // Create the element.
    549                 el = new CKEDITOR.dom.element( elementName, targetDocument );
    550 
    551                 // Assign all defined attributes.
    552                 if ( attributes )
    553                 {
    554                         for ( var att in attributes )
     675       
     676                        // Assign all defined styles.
     677                        if ( styles )
    555678                        {
    556                                 var attValue = attributes[ att ];
    557                                 if ( attValue && variables )
     679                                for ( var styleName in styles )
     680                                        el.setStyle( styleName, styles[ styleName ] );
     681       
     682                                if ( variables )
    558683                                {
    559                                         attValue = attValue.replace( styleVariableAttNameRegex, function()
     684                                        attValue = el.getAttribute( 'style' ).replace( styleVariableAttNameRegex, function()
    560685                                                {
    561686                                                        // The second group in the regex is the variable name.
    562687                                                        return variables[ arguments[2] ] || arguments[0];
     
    561686                                                        // The second group in the regex is the variable name.
    562687                                                        return variables[ arguments[2] ] || arguments[0];
    563688                                                });
     689                                        el.setAttribute( 'style', attValue );
    564690                                }
    565                                 el.setAttribute( att, attValue );
    566691                        }
    567                 }
     692       
     693                        // Save the created element. It will be reused on future calls.
     694                        return ( style._.element = el );
     695                };
     696       
     697})();
     698/**
     699 * Registers a function to be called whenever a style changes its state in the
     700 * editing area. The current state is passed to the function. The possible
     701 * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.
     702 * @param {CKEDITOR.style} The style to be watched.
     703 * @param {Function} The function to be called when the style state changes.
     704 * @example
     705 * // Create a style object for the <b> element.
     706 * var style = new CKEDITOR.style( { element : 'b' } );
     707 * var editor = CKEDITOR.instances.editor1;
     708 * editor.attachStyleStateChange( style, function( state )
     709 *     {
     710 *         if ( state == CKEDITOR.TRISTATE_ON )
     711 *             alert( 'The current state for the B element is ON' );
     712 *         else
     713 *             alert( 'The current state for the B element is OFF' );
     714 *     });
     715 */
     716CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback )
     717{
     718                // Try to get the list of attached callbacks.
     719                var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
    568720
    569                 // Assign all defined styles.
    570                 if ( styles )
     721                // If it doesn't exist, it means this is the first call. So, let's create
     722                // all the structure to manage the style checks and the callback calls.
     723                if ( !styleStateChangeCallbacks )
    571724                {
    572                         for ( var styleName in styles )
    573                                 el.setStyle( styleName, styles[ styleName ] );
     725                                // Create the callbacks array.
     726                                styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
    574727
    575                         if ( variables )
    576                         {
    577                                 attValue = el.getAttribute( 'style' ).replace( styleVariableAttNameRegex, function()
    578                                         {
    579                                                 // The second group in the regex is the variable name.
    580                                                 return variables[ arguments[2] ] || arguments[0];
    581                                         });
    582                                 el.setAttribute( 'style', attValue );
    583                         }
     728                                // Attach to the selectionChange event, so we can check the styles at
     729                                // that point.
     730                                this.on( 'selectionChange', function( ev )
     731                                                {
     732                                                                // Loop throw all registered callbacks.
     733                                                                for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
     734                                                                {
     735                                                                                var callback = styleStateChangeCallbacks[ i ];
     736
     737                                                                                // Check the current state for the style defined for that
     738                                                                                // callback.
     739                                                                                var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
     740
     741                                                                                // If the state changed since the last check.
     742                                                                                if ( callback.state !== currentState )
     743                                                                                {
     744                                                                                                // Call the callback function, passing the current
     745                                                                                                // state to it.
     746                                                                                                callback.fn.call( this, currentState );
     747
     748                                                                                                // Save the current state, so it can be compared next
     749                                                                                                // time.
     750                                                                                                callback.state !== currentState;
     751                                                                                }
     752                                                                }
     753                                                });
    584754                }
    585755
    586                 // Save the created element. It will be reused on future calls.
    587                 return ( style._.element = el );
    588         };
    589 })();
     756                // Save the callback info, so it can be checked on the next occurence of
     757                // selectionChange.
     758                styleStateChangeCallbacks.push( { style : style, fn : callback } );
     759};
     760
     761CKEDITOR.STYLE_BLOCK = 1;
     762CKEDITOR.STYLE_INLINE = 2;
     763CKEDITOR.STYLE_OBJECT = 3;
     764
    590765
    591766CKEDITOR.styleCommand = function( style )
    592767{
     
    595770
    596771CKEDITOR.styleCommand.prototype.exec = function( editor )
    597772{
    598         editor.focus();
    599 
    600         var doc = editor.document;
    601 
    602         if ( doc )
    603                 this.style.apply( doc );
    604 
    605         return !!doc;
    606 };
     773        var editor = this.style._.editor;
     774        if(this.state === CKEDITOR.TRISTATE_OFF)       
     775        {
     776                this.style.apply();
     777        }
     778        else if(this.state === CKEDITOR.TRISTATE_ON)
     779        {
     780                this.style.reverse();
     781        }
     782};
     783 No newline at end of file
  • _source/plugins/selection/plugin.js

     
    9999
    100100                                                editor.document.on( 'mouseup', checkSelectionChangeTimeout, editor );
    101101                                                editor.document.on( 'keyup', checkSelectionChangeTimeout, editor );
     102                                                // Check selection after commands applied.
     103                                                editor.on( 'afterCommandExec', checkSelectionChangeTimeout, editor );
     104                                       
    102105                                        }
    103106                                });
    104107
© 2003 – 2022, CKSource sp. z o.o. sp.k. All rights reserved. | Terms of use | Privacy policy