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