source: trunk/filemanager/tp/ckeditor/_source/plugins/styles/plugin.js @ 2000

Revision 2000, 37.5 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Implementação do módulo gerenciador de arquivos

Line 
1/*
2Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6CKEDITOR.plugins.add( 'styles',
7{
8        requires : [ 'selection' ]
9});
10
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 */
29CKEDITOR.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 )
44                        {
45                                // Loop throw all registered callbacks.
46                                for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
47                                {
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 )
56                                        {
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;
64                                        }
65                                }
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
74CKEDITOR.STYLE_BLOCK = 1;
75CKEDITOR.STYLE_INLINE = 2;
76CKEDITOR.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        var semicolonFixRegex = /\s*(?:;\s*|$)/;
84
85        CKEDITOR.style = function( styleDefinition, variablesValues )
86        {
87                if ( variablesValues )
88                {
89                        styleDefinition = CKEDITOR.tools.clone( styleDefinition );
90
91                        replaceVariables( styleDefinition.attributes, variablesValues );
92                        replaceVariables( styleDefinition.styles, variablesValues );
93                }
94
95                var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
96
97                this.type =
98                        ( element == '#' || blockElements[ element ] ) ?
99                                CKEDITOR.STYLE_BLOCK
100                        : objectElements[ element ] ?
101                                CKEDITOR.STYLE_OBJECT
102                        :
103                                CKEDITOR.STYLE_INLINE;
104
105                this._ =
106                {
107                        definition : styleDefinition
108                };
109        };
110
111        CKEDITOR.style.prototype =
112        {
113                apply : function( document )
114                {
115                        applyStyle.call( this, document, false );
116                },
117
118                remove : function( document )
119                {
120                        applyStyle.call( this, document, true );
121                },
122
123                applyToRange : function( range )
124                {
125                        return ( this.applyToRange =
126                                                this.type == CKEDITOR.STYLE_INLINE ?
127                                                        applyInlineStyle
128                                                : this.type == CKEDITOR.STYLE_BLOCK ?
129                                                        applyBlockStyle
130                                                : null ).call( this, range );
131                },
132
133                removeFromRange : function( range )
134                {
135                        return ( this.removeFromRange =
136                                                this.type == CKEDITOR.STYLE_INLINE ?
137                                                        removeInlineStyle
138                                                : null ).call( this, range );
139                },
140
141                applyToObject : function( element )
142                {
143                        setupElement( element, this );
144                },
145
146                /**
147                 * Get the style state inside an element path. Returns "true" if the
148                 * element is active in the path.
149                 */
150                checkActive : function( elementPath )
151                {
152                        switch ( this.type )
153                        {
154                                case CKEDITOR.STYLE_BLOCK :
155                                        return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
156
157                                case CKEDITOR.STYLE_INLINE :
158
159                                        var elements = elementPath.elements;
160
161                                        for ( var i = 0, element ; i < elements.length ; i++ )
162                                        {
163                                                element = elements[i];
164
165                                                if ( element == elementPath.block || element == elementPath.blockLimit )
166                                                        continue;
167
168                                                if ( this.checkElementRemovable( element, true ) )
169                                                        return true;
170                                        }
171                        }
172                        return false;
173                },
174
175                // Checks if an element, or any of its attributes, is removable by the
176                // current style definition.
177                checkElementRemovable : function( element, fullMatch )
178                {
179                        if ( !element )
180                                return false;
181
182                        var def = this._.definition,
183                                attribs;
184
185                        // If the element name is the same as the style name.
186                        if ( element.getName() == this.element )
187                        {
188                                // If no attributes are defined in the element.
189                                if ( !fullMatch && !element.hasAttributes() )
190                                        return true;
191
192                                attribs = getAttributesForComparison( def );
193
194                                if ( attribs._length )
195                                {
196                                        for ( var attName in attribs )
197                                        {
198                                                if ( attName == '_length' )
199                                                        continue;
200
201                                                var elementAttr = element.getAttribute( attName );
202                                                if ( attribs[attName] ==
203                                                         ( attName == 'style' ?
204                                                           normalizeCssText( elementAttr, false ) : elementAttr  ) )
205                                                {
206                                                        if ( !fullMatch )
207                                                                return true;
208                                                }
209                                                else if ( fullMatch )
210                                                                return false;
211                                        }
212                                        if( fullMatch )
213                                                return true;
214                                }
215                                else
216                                        return true;
217                        }
218
219                        // Check if the element can be somehow overriden.
220                        var override = getOverrides( this )[ element.getName() ] ;
221                        if ( override )
222                        {
223                                // If no attributes have been defined, remove the element.
224                                if ( !( attribs = override.attributes ) )
225                                        return true;
226
227                                for ( var i = 0 ; i < attribs.length ; i++ )
228                                {
229                                        attName = attribs[i][0];
230                                        var actualAttrValue = element.getAttribute( attName );
231                                        if ( actualAttrValue )
232                                        {
233                                                var attValue = attribs[i][1];
234
235                                                // Remove the attribute if:
236                                                //    - The override definition value is null;
237                                                //    - The override definition value is a string that
238                                                //      matches the attribute value exactly.
239                                                //    - The override definition value is a regex that
240                                                //      has matches in the attribute value.
241                                                if ( attValue === null ||
242                                                                ( typeof attValue == 'string' && actualAttrValue == attValue ) ||
243                                                                attValue.test( actualAttrValue ) )
244                                                        return true;
245                                        }
246                                }
247                        }
248                        return false;
249                }
250        };
251
252        // Build the cssText based on the styles definition.
253        CKEDITOR.style.getStyleText = function( styleDefinition )
254        {
255                // If we have already computed it, just return it.
256                var stylesDef = styleDefinition._ST;
257                if ( stylesDef )
258                        return stylesDef;
259
260                stylesDef = styleDefinition.styles;
261
262                // Builds the StyleText.
263
264                var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '';
265
266                if ( stylesText.length )
267                        stylesText = stylesText.replace( semicolonFixRegex, ';' );
268
269                for ( var style in stylesDef )
270                        stylesText += ( style + ':' + stylesDef[ style ] ).replace( semicolonFixRegex, ';' );
271
272                // Browsers make some changes to the style when applying them. So, here
273                // we normalize it to the browser format.
274                if ( stylesText.length )
275                        stylesText = normalizeCssText( stylesText );
276
277                // Return it, saving it to the next request.
278                return ( styleDefinition._ST = stylesText );
279        };
280
281        function applyInlineStyle( range )
282        {
283                var document = range.document;
284
285                if ( range.collapsed )
286                {
287                        // Create the element to be inserted in the DOM.
288                        var collapsedElement = getElement( this, document );
289
290                        // Insert the empty element into the DOM at the range position.
291                        range.insertNode( collapsedElement );
292
293                        // Place the selection right inside the empty element.
294                        range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
295
296                        return;
297                }
298
299                var elementName = this.element;
300                var def = this._.definition;
301                var isUnknownElement;
302
303                // Get the DTD definition for the element. Defaults to "span".
304                var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
305
306                // Bookmark the range so we can re-select it after processing.
307                var bookmark = range.createBookmark();
308
309                // Expand the range.
310                range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
311                range.trim();
312
313                // Get the first node to be processed and the last, which concludes the
314                // processing.
315                var boundaryNodes = range.getBoundaryNodes();
316                var firstNode = boundaryNodes.startNode;
317                var lastNode = boundaryNodes.endNode.getNextSourceNode( true );
318
319                // Probably the document end is reached, we need a marker node.
320                if ( !lastNode )
321                {
322                                var marker;
323                                lastNode = marker = document.createText( '' );
324                                lastNode.insertAfter( range.endContainer );
325                }
326                // The detection algorithm below skips the contents inside bookmark nodes, so
327                // we'll need to make sure lastNode isn't the &nbsp; inside a bookmark node.
328                var lastParent = lastNode.getParent();
329                if ( lastParent && lastParent.getAttribute( '_fck_bookmark' ) )
330                        lastNode = lastParent;
331
332                if ( lastNode.equals( firstNode ) )
333                {
334                        // If the last node is the same as the the first one, we must move
335                        // it to the next one, otherwise the first one will not be
336                        // processed.
337                        lastNode = lastNode.getNextSourceNode( true );
338
339                        // It may happen that there are no more nodes after it (the end of
340                        // the document), so we must add something there to make our code
341                        // simpler.
342                        if ( !lastNode )
343                        {
344                                lastNode = marker = document.createText( '' );
345                                lastNode.insertAfter( firstNode );
346                        }
347                }
348
349                var currentNode = firstNode;
350
351                var styleRange;
352
353                // Indicates that that some useful inline content has been found, so
354                // the style should be applied.
355                var hasContents;
356
357                while ( currentNode )
358                {
359                        var applyStyle = false;
360
361                        if ( currentNode.equals( lastNode ) )
362                        {
363                                currentNode = null;
364                                applyStyle = true;
365                        }
366                        else
367                        {
368                                var nodeType = currentNode.type;
369                                var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
370
371                                if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
372                                {
373                                        currentNode = currentNode.getNextSourceNode( true );
374                                        continue;
375                                }
376
377                                // Check if the current node can be a child of the style element.
378                                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 ) ) )
379                                {
380                                        var currentParent = currentNode.getParent();
381
382                                        // Check if the style element can be a child of the current
383                                        // node parent or if the element is not defined in the DTD.
384                                        if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
385                                        {
386                                                // This node will be part of our range, so if it has not
387                                                // been started, place its start right before the node.
388                                                // In the case of an element node, it will be included
389                                                // only if it is entirely inside the range.
390                                                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 ) ) )
391                                                {
392                                                        styleRange = new CKEDITOR.dom.range( document );
393                                                        styleRange.setStartBefore( currentNode );
394                                                }
395
396                                                // Non element nodes, or empty elements can be added
397                                                // completely to the range.
398                                                if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) )
399                                                {
400                                                        var includedNode = currentNode;
401                                                        var parentNode;
402
403                                                        // This node is about to be included completelly, but,
404                                                        // if this is the last node in its parent, we must also
405                                                        // check if the parent itself can be added completelly
406                                                        // to the range.
407                                                        while ( !includedNode.$.nextSibling
408                                                                && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
409                                                                && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
410                                                        {
411                                                                includedNode = parentNode;
412                                                        }
413
414                                                        styleRange.setEndAfter( includedNode );
415
416                                                        // If the included node still is the last node in its
417                                                        // parent, it means that the parent can't be included
418                                                        // in this style DTD, so apply the style immediately.
419                                                        if ( !includedNode.$.nextSibling )
420                                                                applyStyle = true;
421
422                                                        if ( !hasContents )
423                                                                hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
424                                                }
425                                        }
426                                        else
427                                                applyStyle = true;
428                                }
429                                else
430                                        applyStyle = true;
431
432                                // Get the next node to be processed.
433                                currentNode = currentNode.getNextSourceNode();
434                        }
435
436                        // Apply the style if we have something to which apply it.
437                        if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
438                        {
439                                // Build the style element, based on the style object definition.
440                                var styleNode = getElement( this, document );
441
442                                // Get the element that holds the entire range.
443                                var parent = styleRange.getCommonAncestor();
444
445                                // Loop through the parents, removing the redundant attributes
446                                // from the element to be applied.
447                                while ( styleNode && parent )
448                                {
449                                        if ( parent.getName() == elementName )
450                                        {
451                                                for ( var attName in def.attributes )
452                                                {
453                                                        if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
454                                                                styleNode.removeAttribute( attName );
455                                                }
456
457                                                for ( var styleName in def.styles )
458                                                {
459                                                        if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
460                                                                styleNode.removeStyle( styleName );
461                                                }
462
463                                                if ( !styleNode.hasAttributes() )
464                                                {
465                                                        styleNode = null;
466                                                        break;
467                                                }
468                                        }
469
470                                        parent = parent.getParent();
471                                }
472
473                                if ( styleNode )
474                                {
475                                        // Move the contents of the range to the style element.
476                                        styleRange.extractContents().appendTo( styleNode );
477
478                                        // Here we do some cleanup, removing all duplicated
479                                        // elements from the style element.
480                                        removeFromInsideElement( this, styleNode );
481
482                                        // Insert it into the range position (it is collapsed after
483                                        // extractContents.
484                                        styleRange.insertNode( styleNode );
485
486                                        // Let's merge our new style with its neighbors, if possible.
487                                        mergeSiblings( styleNode );
488
489                                        // As the style system breaks text nodes constantly, let's normalize
490                                        // things for performance.
491                                        // With IE, some paragraphs get broken when calling normalize()
492                                        // repeatedly. Also, for IE, we must normalize body, not documentElement.
493                                        // IE is also known for having a "crash effect" with normalize().
494                                        // We should try to normalize with IE too in some way, somewhere.
495                                        if ( !CKEDITOR.env.ie )
496                                                styleNode.$.normalize();
497                                }
498
499                                // Style applied, let's release the range, so it gets
500                                // re-initialization in the next loop.
501                                styleRange = null;
502                        }
503                }
504
505                // Remove the temporary marking node.(#4111)
506                marker && marker.remove();
507                range.moveToBookmark( bookmark );
508        }
509
510        function removeInlineStyle( range )
511        {
512                /*
513                 * Make sure our range has included all "collpased" parent inline nodes so
514                 * that our operation logic can be simpler.
515                 */
516                range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
517
518                var bookmark = range.createBookmark(),
519                        startNode = bookmark.startNode;
520
521                if ( range.collapsed )
522                {
523
524                        var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
525                                // The topmost element in elementspatch which we should jump out of.
526                                boundaryElement;
527
528
529                        for ( var i = 0, element ; i < startPath.elements.length
530                                        && ( element = startPath.elements[i] ) ; i++ )
531                        {
532                                /*
533                                 * 1. If it's collaped inside text nodes, try to remove the style from the whole element.
534                                 *
535                                 * 2. Otherwise if it's collapsed on element boundaries, moving the selection
536                                 *  outside the styles instead of removing the whole tag,
537                                 *  also make sure other inner styles were well preserverd.(#3309)
538                                 */
539                                if ( element == startPath.block || element == startPath.blockLimit )
540                                        break;
541
542                                if ( this.checkElementRemovable( element ) )
543                                {
544                                        var endOfElement = range.checkBoundaryOfElement( element, CKEDITOR.END ),
545                                                        startOfElement = !endOfElement && range.checkBoundaryOfElement( element, CKEDITOR.START );
546                                        if ( startOfElement || endOfElement )
547                                        {
548                                                boundaryElement = element;
549                                                boundaryElement.match = startOfElement ? 'start' : 'end';
550                                        }
551                                        else
552                                        {
553                                                /*
554                                                 * Before removing the style node, there may be a sibling to the style node
555                                                 * that's exactly the same to the one to be removed. To the user, it makes
556                                                 * no difference that they're separate entities in the DOM tree. So, merge
557                                                 * them before removal.
558                                                 */
559                                                mergeSiblings( element );
560                                                removeFromElement( this, element );
561
562                                        }
563                                }
564                        }
565
566                        // Re-create the style tree after/before the boundary element,
567                        // the replication start from bookmark start node to define the
568                        // new range.
569                        if ( boundaryElement )
570                        {
571                                var clonedElement = startNode;
572                                for ( i = 0 ;; i++ )
573                                {
574                                        var newElement = startPath.elements[ i ];
575                                        if ( newElement.equals( boundaryElement ) )
576                                                break;
577                                        // Avoid copying any matched element.
578                                        else if( newElement.match )
579                                                continue;
580                                        else
581                                                newElement = newElement.clone();
582                                        newElement.append( clonedElement );
583                                        clonedElement = newElement;
584                                }
585                                clonedElement[ boundaryElement.match == 'start' ?
586                                                        'insertBefore' : 'insertAfter' ]( boundaryElement );
587                        }
588                }
589                else
590                {
591                        /*
592                         * Now our range isn't collapsed. Lets walk from the start node to the end
593                         * node via DFS and remove the styles one-by-one.
594                         */
595                        var endNode = bookmark.endNode,
596                                me = this;
597
598                        /*
599                         * Find out the style ancestor that needs to be broken down at startNode
600                         * and endNode.
601                         */
602                        function breakNodes()
603                        {
604                                var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
605                                        endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ),
606                                        breakStart = null,
607                                        breakEnd = null;
608                                for ( var i = 0 ; i < startPath.elements.length ; i++ )
609                                {
610                                        var element = startPath.elements[ i ];
611
612                                        if ( element == startPath.block || element == startPath.blockLimit )
613                                                break;
614
615                                        if ( me.checkElementRemovable( element ) )
616                                                breakStart = element;
617                                }
618                                for ( i = 0 ; i < endPath.elements.length ; i++ )
619                                {
620                                        element = endPath.elements[ i ];
621
622                                        if ( element == endPath.block || element == endPath.blockLimit )
623                                                break;
624
625                                        if ( me.checkElementRemovable( element ) )
626                                                breakEnd = element;
627                                }
628
629                                if ( breakEnd )
630                                        endNode.breakParent( breakEnd );
631                                if ( breakStart )
632                                        startNode.breakParent( breakStart );
633                        }
634                        breakNodes();
635
636                        // Now, do the DFS walk.
637                        var currentNode = startNode.getNext();
638                        while ( !currentNode.equals( endNode ) )
639                        {
640                                /*
641                                 * Need to get the next node first because removeFromElement() can remove
642                                 * the current node from DOM tree.
643                                 */
644                                var nextNode = currentNode.getNextSourceNode();
645                                if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) )
646                                {
647                                        // Remove style from element or overriding element.
648                                        if( currentNode.getName() == this.element )
649                                                removeFromElement( this, currentNode );
650                                        else
651                                                removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] );
652
653                                        /*
654                                         * removeFromElement() may have merged the next node with something before
655                                         * the startNode via mergeSiblings(). In that case, the nextNode would
656                                         * contain startNode and we'll have to call breakNodes() again and also
657                                         * reassign the nextNode to something after startNode.
658                                         */
659                                        if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) )
660                                        {
661                                                breakNodes();
662                                                nextNode = startNode.getNext();
663                                        }
664                                }
665                                currentNode = nextNode;
666                        }
667                }
668
669                range.moveToBookmark( bookmark );
670}
671
672        function applyBlockStyle( range )
673        {
674                // Serializible bookmarks is needed here since
675                // elements may be merged.
676                var bookmark = range.createBookmark( true );
677
678                var iterator = range.createIterator();
679                iterator.enforceRealBlocks = true;
680
681                var block;
682                var doc = range.document;
683                var previousPreBlock;
684
685                while( ( block = iterator.getNextParagraph() ) )                // Only one =
686                {
687                        var newBlock = getElement( this, doc );
688                        replaceBlock( block, newBlock );
689                }
690
691                range.moveToBookmark( bookmark );
692        }
693
694        // Replace the original block with new one, with special treatment
695        // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
696        // when necessary.(#3188)
697        function replaceBlock( block, newBlock )
698        {
699                var newBlockIsPre       = newBlock.is( 'pre' );
700                var blockIsPre          = block.is( 'pre' );
701
702                var isToPre     = newBlockIsPre && !blockIsPre;
703                var isFromPre   = !newBlockIsPre && blockIsPre;
704
705                if ( isToPre )
706                        newBlock = toPre( block, newBlock );
707                else if ( isFromPre )
708                        // Split big <pre> into pieces before start to convert.
709                        newBlock = fromPres( splitIntoPres( block ), newBlock );
710                else
711                        block.moveChildren( newBlock );
712
713                newBlock.replace( block );
714
715                if ( newBlockIsPre )
716                {
717                        // Merge previous <pre> blocks.
718                        mergePre( newBlock );
719                }
720        }
721
722        /**
723         * Merge a <pre> block with a previous sibling if available.
724         */
725        function mergePre( preBlock )
726        {
727                var previousBlock;
728                if ( !( ( previousBlock = preBlock.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) )
729                                 && previousBlock.is
730                                 && previousBlock.is( 'pre') ) )
731                        return;
732
733                // Merge the previous <pre> block contents into the current <pre>
734                // block.
735                //
736                // Another thing to be careful here is that currentBlock might contain
737                // a '\n' at the beginning, and previousBlock might contain a '\n'
738                // towards the end. These new lines are not normally displayed but they
739                // become visible after merging.
740                var mergedHtml = replace( previousBlock.getHtml(), /\n$/, '' ) + '\n\n' +
741                                replace( preBlock.getHtml(), /^\n/, '' ) ;
742
743                // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces.
744                if ( CKEDITOR.env.ie )
745                        preBlock.$.outerHTML = '<pre>' + mergedHtml + '</pre>';
746                else
747                        preBlock.setHtml( mergedHtml );
748
749                previousBlock.remove();
750        }
751
752        /**
753         * Split into multiple <pre> blocks separated by double line-break.
754         * @param preBlock
755         */
756        function splitIntoPres( preBlock )
757        {
758                // Exclude the ones at header OR at tail,
759                // and ignore bookmark content between them.
760                var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+_fck_bookmark.*?\/span>))*\n(?!$)/gi,
761                        blockName = preBlock.getName(),
762                        splitedHtml = replace( preBlock.getOuterHtml(),
763                                duoBrRegex,
764                                function( match, charBefore, bookmark )
765                                {
766                                  return charBefore + '</pre>' + bookmark + '<pre>';
767                                } );
768
769                var pres = [];
770                splitedHtml.replace( /<pre>([\s\S]*?)<\/pre>/gi, function( match, preContent ){
771                        pres.push( preContent );
772                } );
773                return pres;
774        }
775
776        // Wrapper function of String::replace without considering of head/tail bookmarks nodes.
777        function replace( str, regexp, replacement )
778        {
779                var headBookmark = '',
780                        tailBookmark = '';
781
782                str = str.replace( /(^<span[^>]+_fck_bookmark.*?\/span>)|(<span[^>]+_fck_bookmark.*?\/span>$)/gi,
783                        function( str, m1, m2 ){
784                                        m1 && ( headBookmark = m1 );
785                                        m2 && ( tailBookmark = m2 );
786                                return '';
787                        } );
788                return headBookmark + str.replace( regexp, replacement ) + tailBookmark;
789        }
790        /**
791         * Converting a list of <pre> into blocks with format well preserved.
792         */
793        function fromPres( preHtmls, newBlock )
794        {
795                var docFrag = new CKEDITOR.dom.documentFragment( newBlock.getDocument() );
796                for ( var i = 0 ; i < preHtmls.length ; i++ )
797                {
798                        var blockHtml = preHtmls[ i ];
799
800                        // 1. Trim the first and last line-breaks immediately after and before <pre>,
801                        // they're not visible.
802                         blockHtml =  blockHtml.replace( /(\r\n|\r)/g, '\n' ) ;
803                         blockHtml = replace(  blockHtml, /^[ \t]*\n/, '' ) ;
804                         blockHtml = replace(  blockHtml, /\n$/, '' ) ;
805                        // 2. Convert spaces or tabs at the beginning or at the end to &nbsp;
806                         blockHtml = replace(  blockHtml, /^[ \t]+|[ \t]+$/g, function( match, offset, s )
807                                        {
808                                                if ( match.length == 1 )        // one space, preserve it
809                                                        return '&nbsp;' ;
810                                                else if ( !offset )             // beginning of block
811                                                        return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
812                                                else                            // end of block
813                                                        return ' ' + CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 );
814                                        } ) ;
815
816                        // 3. Convert \n to <BR>.
817                        // 4. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
818                         blockHtml =  blockHtml.replace( /\n/g, '<br>' ) ;
819                         blockHtml =  blockHtml.replace( /[ \t]{2,}/g,
820                                        function ( match )
821                                        {
822                                                return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ' ;
823                                        } ) ;
824
825                        var newBlockClone = newBlock.clone();
826                        newBlockClone.setHtml(  blockHtml );
827                        docFrag.append( newBlockClone );
828                }
829                return docFrag;
830        }
831
832        /**
833         * Converting from a non-PRE block to a PRE block in formatting operations.
834         */
835        function toPre( block, newBlock )
836        {
837                // First trim the block content.
838                var preHtml = block.getHtml();
839
840                // 1. Trim head/tail spaces, they're not visible.
841                preHtml = replace( preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, '' );
842                // 2. Delete ANSI whitespaces immediately before and after <BR> because
843                //    they are not visible.
844                preHtml = preHtml.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1' );
845                // 3. Compress other ANSI whitespaces since they're only visible as one
846                //    single space previously.
847                // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
848                preHtml = preHtml.replace( /([ \t\n\r]+|&nbsp;)/g, ' ' );
849                // 5. Convert any <BR /> to \n. This must not be done earlier because
850                //    the \n would then get compressed.
851                preHtml = preHtml.replace( /<br\b[^>]*>/gi, '\n' );
852
853                // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces.
854                if ( CKEDITOR.env.ie )
855                {
856                        var temp = block.getDocument().createElement( 'div' );
857                        temp.append( newBlock );
858                        newBlock.$.outerHTML =  '<pre>' + preHtml + '</pre>';
859                        newBlock = temp.getFirst().remove();
860                }
861                else
862                        newBlock.setHtml( preHtml );
863
864                return newBlock;
865        }
866
867        // Removes a style from an element itself, don't care about its subtree.
868        function removeFromElement( style, element )
869        {
870                var def = style._.definition,
871                        attributes = def.attributes,
872                        styles = def.styles,
873                        overrides = getOverrides( style );
874
875                function removeAttrs()
876                {
877                        for ( var attName in attributes )
878                        {
879                                // The 'class' element value must match (#1318).
880                                if ( attName == 'class' && element.getAttribute( attName ) != attributes[ attName ] )
881                                        continue;
882                                element.removeAttribute( attName );
883                        }
884                }
885
886                // Remove definition attributes/style from the elemnt.
887                removeAttrs();
888                for ( var styleName in styles )
889                        element.removeStyle( styleName );
890
891                // Now remove override styles on the element.
892                attributes = overrides[ element.getName() ];
893                if( attributes )
894                        removeAttrs();
895                removeNoAttribsElement( element );
896        }
897
898        // Removes a style from inside an element.
899        function removeFromInsideElement( style, element )
900        {
901                var def = style._.definition,
902                        attribs = def.attributes,
903                        styles = def.styles,
904                        overrides = getOverrides( style );
905
906                var innerElements = element.getElementsByTag( style.element );
907
908                for ( var i = innerElements.count(); --i >= 0 ; )
909                        removeFromElement( style,  innerElements.getItem( i ) );
910
911                // Now remove any other element with different name that is
912                // defined to be overriden.
913                for ( var overrideElement in overrides )
914                {
915                        if ( overrideElement != style.element )
916                        {
917                                innerElements = element.getElementsByTag( overrideElement ) ;
918                                for ( i = innerElements.count() - 1 ; i >= 0 ; i-- )
919                                {
920                                        var innerElement = innerElements.getItem( i );
921                                        removeOverrides( innerElement, overrides[ overrideElement ] ) ;
922                                }
923                        }
924                }
925
926        }
927
928        /**
929         *  Remove overriding styles/attributes from the specific element.
930         *  Note: Remove the element if no attributes remain.
931         * @param {Object} element
932         * @param {Object} overrides
933         */
934        function removeOverrides( element, overrides )
935        {
936                var attributes = overrides && overrides.attributes ;
937
938                if ( attributes )
939                {
940                        for ( var i = 0 ; i < attributes.length ; i++ )
941                        {
942                                var attName = attributes[i][0], actualAttrValue ;
943
944                                if ( ( actualAttrValue = element.getAttribute( attName ) ) )
945                                {
946                                        var attValue = attributes[i][1] ;
947
948                                        // Remove the attribute if:
949                                        //    - The override definition value is null ;
950                                        //    - The override definition valie is a string that
951                                        //      matches the attribute value exactly.
952                                        //    - The override definition value is a regex that
953                                        //      has matches in the attribute value.
954                                        if ( attValue === null ||
955                                                        ( attValue.test && attValue.test( actualAttrValue ) ) ||
956                                                        ( typeof attValue == 'string' && actualAttrValue == attValue ) )
957                                                element.removeAttribute( attName ) ;
958                                }
959                        }
960                }
961
962                removeNoAttribsElement( element );
963        }
964
965        // If the element has no more attributes, remove it.
966        function removeNoAttribsElement( element )
967        {
968                // If no more attributes remained in the element, remove it,
969                // leaving its children.
970                if ( !element.hasAttributes() )
971                {
972                        // Removing elements may open points where merging is possible,
973                        // so let's cache the first and last nodes for later checking.
974                        var firstChild  = element.getFirst();
975                        var lastChild   = element.getLast();
976
977                        element.remove( true );
978
979                        if ( firstChild )
980                        {
981                                // Check the cached nodes for merging.
982                                mergeSiblings( firstChild );
983
984                                if ( lastChild && !firstChild.equals( lastChild ) )
985                                        mergeSiblings( lastChild );
986                        }
987                }
988        }
989
990        function mergeSiblings( element )
991        {
992                if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
993                        return;
994
995                mergeElements( element, element.getNext(), true );
996                mergeElements( element, element.getPrevious() );
997        }
998
999        function mergeElements( element, sibling, isNext )
1000        {
1001                if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
1002                {
1003                        var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
1004
1005                        if ( hasBookmark )
1006                                sibling = isNext ? sibling.getNext() : sibling.getPrevious();
1007
1008                        if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && element.isIdentical( sibling ) )
1009                        {
1010                                // Save the last child to be checked too, to merge things like
1011                                // <b><i></i></b><b><i></i></b> => <b><i></i></b>
1012                                var innerSibling = isNext ? element.getLast() : element.getFirst();
1013
1014                                if ( hasBookmark )
1015                                        ( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
1016
1017                                sibling.moveChildren( element, !isNext );
1018                                sibling.remove();
1019
1020                                // Now check the last inner child (see two comments above).
1021                                if ( innerSibling )
1022                                        mergeSiblings( innerSibling );
1023                        }
1024                }
1025        }
1026
1027        function getElement( style, targetDocument )
1028        {
1029                var el;
1030
1031                var def = style._.definition;
1032
1033                var elementName = style.element;
1034
1035                // The "*" element name will always be a span for this function.
1036                if ( elementName == '*' )
1037                        elementName = 'span';
1038
1039                // Create the element.
1040                el = new CKEDITOR.dom.element( elementName, targetDocument );
1041
1042                return setupElement( el, style );
1043        }
1044
1045        function setupElement( el, style )
1046        {
1047                var def = style._.definition;
1048                var attributes = def.attributes;
1049                var styles = CKEDITOR.style.getStyleText( def );
1050
1051                // Assign all defined attributes.
1052                if ( attributes )
1053                {
1054                        for ( var att in attributes )
1055                        {
1056                                el.setAttribute( att, attributes[ att ] );
1057                        }
1058                }
1059
1060                // Assign all defined styles.
1061                if ( styles )
1062                        el.setAttribute( 'style', styles );
1063
1064                return el;
1065        }
1066
1067        var varRegex = /#\((.+?)\)/g;
1068        function replaceVariables( list, variablesValues )
1069        {
1070                for ( var item in list )
1071                {
1072                        list[ item ] = list[ item ].replace( varRegex, function( match, varName )
1073                                {
1074                                        return variablesValues[ varName ];
1075                                });
1076                }
1077        }
1078
1079
1080        // Returns an object that can be used for style matching comparison.
1081        // Attributes names and values are all lowercased, and the styles get
1082        // merged with the style attribute.
1083        function getAttributesForComparison( styleDefinition )
1084        {
1085                // If we have already computed it, just return it.
1086                var attribs = styleDefinition._AC;
1087                if ( attribs )
1088                        return attribs;
1089
1090                attribs = {};
1091
1092                var length = 0;
1093
1094                // Loop through all defined attributes.
1095                var styleAttribs = styleDefinition.attributes;
1096                if ( styleAttribs )
1097                {
1098                        for ( var styleAtt in styleAttribs )
1099                        {
1100                                length++;
1101                                attribs[ styleAtt ] = styleAttribs[ styleAtt ];
1102                        }
1103                }
1104
1105                // Includes the style definitions.
1106                var styleText = CKEDITOR.style.getStyleText( styleDefinition );
1107                if ( styleText )
1108                {
1109                        if ( !attribs[ 'style' ] )
1110                                length++;
1111                        attribs[ 'style' ] = styleText;
1112                }
1113
1114                // Appends the "length" information to the object.
1115                attribs._length = length;
1116
1117                // Return it, saving it to the next request.
1118                return ( styleDefinition._AC = attribs );
1119        }
1120
1121        /**
1122         * Get the the collection used to compare the elements and attributes,
1123         * defined in this style overrides, with other element. All information in
1124         * it is lowercased.
1125         * @param {CKEDITOR.style} style
1126         */
1127        function getOverrides( style )
1128        {
1129                if( style._.overrides )
1130                        return style._.overrides;
1131
1132                var overrides = ( style._.overrides = {} ),
1133                        definition = style._.definition.overrides;
1134
1135                if ( definition )
1136                {
1137                        // The override description can be a string, object or array.
1138                        // Internally, well handle arrays only, so transform it if needed.
1139                        if ( !CKEDITOR.tools.isArray( definition ) )
1140                                definition = [ definition ];
1141
1142                        // Loop through all override definitions.
1143                        for ( var i = 0 ; i < definition.length ; i++ )
1144                        {
1145                                var override = definition[i];
1146                                var elementName;
1147                                var overrideEl;
1148                                var attrs;
1149
1150                                // If can be a string with the element name.
1151                                if ( typeof override == 'string' )
1152                                        elementName = override.toLowerCase();
1153                                // Or an object.
1154                                else
1155                                {
1156                                        elementName = override.element ? override.element.toLowerCase() : style.element;
1157                                        attrs = override.attributes;
1158                                }
1159
1160                                // We can have more than one override definition for the same
1161                                // element name, so we attempt to simply append information to
1162                                // it if it already exists.
1163                                overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} );
1164
1165                                if ( attrs )
1166                                {
1167                                        // The returning attributes list is an array, because we
1168                                        // could have different override definitions for the same
1169                                        // attribute name.
1170                                        var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || new Array() );
1171                                        for ( var attName in attrs )
1172                                        {
1173                                                // Each item in the attributes array is also an array,
1174                                                // where [0] is the attribute name and [1] is the
1175                                                // override value.
1176                                                overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] );
1177                                        }
1178                                }
1179                        }
1180                }
1181
1182                return overrides;
1183        }
1184
1185        function normalizeCssText( unparsedCssText, nativeNormalize )
1186        {
1187                var styleText;
1188                if ( nativeNormalize !== false )
1189                {
1190                        // Injects the style in a temporary span object, so the browser parses it,
1191                        // retrieving its final format.
1192                        var temp = new CKEDITOR.dom.element( 'span' );
1193                        temp.setAttribute( 'style', unparsedCssText );
1194                        styleText = temp.getAttribute( 'style' );
1195                }
1196                else
1197                        styleText = unparsedCssText;
1198
1199                // Shrinking white-spaces around colon and semi-colon (#4147).
1200                // Compensate tail semi-colon.
1201                return styleText.replace( /\s*([;:])\s*/, '$1' )
1202                                                         .replace( /([^\s;])$/, '$1;')
1203                                                         .replace( /,\s+/g, ',' ) // Trimming spaces after comma (e.g. font-family name)(#4107).
1204                                                         .toLowerCase();
1205        }
1206
1207        function applyStyle( document, remove )
1208        {
1209                // Get all ranges from the selection.
1210                var selection = document.getSelection();
1211                var ranges = selection.getRanges();
1212                var func = remove ? this.removeFromRange : this.applyToRange;
1213
1214                // Apply the style to the ranges.
1215                for ( var i = 0 ; i < ranges.length ; i++ )
1216                        func.call( this, ranges[ i ] );
1217
1218                // Select the ranges again.
1219                selection.selectRanges( ranges );
1220        }
1221})();
1222
1223CKEDITOR.styleCommand = function( style )
1224{
1225        this.style = style;
1226};
1227
1228CKEDITOR.styleCommand.prototype.exec = function( editor )
1229{
1230        editor.focus();
1231
1232        var doc = editor.document;
1233
1234        if ( doc )
1235        {
1236                if ( this.state == CKEDITOR.TRISTATE_OFF )
1237                        this.style.apply( doc );
1238                else if ( this.state == CKEDITOR.TRISTATE_ON )
1239                        this.style.remove( doc );
1240        }
1241
1242        return !!doc;
1243};
Note: See TracBrowser for help on using the repository browser.