Ticket #2768: 2768.patch
File 2768.patch, 22.9 KB (added by , 15 years ago) |
---|
-
_source/core/editor.js
345 345 execCommand : function( commandName, data ) 346 346 { 347 347 var command = this.getCommand( commandName ); 348 var exeResult; 349 var commandEvent = 350 351 /** 352 * The event data for this command 353 * 354 * @type FCKEDITOR.command.Event 355 */ 356 { 357 name :commandName, 358 command :command 359 }; 348 360 if ( command && command.state != CKEDITOR.TRISTATE_DISABLED ) 349 return command.exec( this, data ); 361 { 362 exeResult = command.exec( this , data ); 363 this.fire( 'afterCommandExec' , commandEvent ); 364 return exeResult; 365 } 350 366 351 367 // throw 'Unknown command name "' + commandName + '"'; 352 368 return false; -
_source/core/tools.js
310 310 return i; 311 311 } 312 312 return -1; 313 }, 314 /** 315 * Binds a function with an object as its this reference. 316 * 317 * @param {Function} 318 * func The function to be bounded. 319 * @param {Object} 320 * obj This this reference to bind to the function. 321 * @returns {Function} The bound function. 322 */ 323 bind : function ( func, obj ) 324 { 325 return function ( ) 326 { 327 return func.apply( obj , arguments ); 328 } 313 329 } 314 330 }; 315 331 -
_source/core/dom/documentFragment.js
8 8 this.$ = CKEDITOR.env.ie ? ownerDocument.$.createElement( 'div' ) : ownerDocument.$.createDocumentFragment(); 9 9 }; 10 10 11 /* 12 * A DocumentFragment behaves like a conventional {@link CKEDITOR.dom.Node} with all of the same 13 * methods, except that when a DocumentFragment is inserted into a Document (or 14 * indeed any other Node that may take children) the children of the 15 * DocumentFragment and not the DocumentFragment itself are inserted into the 16 * Node. 17 */ 18 CKEDITOR.dom.documentFragment.prototype = CKEDITOR.tools 19 .prototypedCopy( CKEDITOR.dom.node.prototype ); 20 11 21 (function() 12 22 { 13 23 var elementPrototype = CKEDITOR.dom.element.prototype; 14 24 15 CKEDITOR.dom.documentFragment.prototype = 16 { 25 CKEDITOR.tools.extend( CKEDITOR.dom.documentFragment.prototype , { 17 26 type : CKEDITOR.NODE_DOCUMENT_FRAGMENT, 18 27 19 28 append : elementPrototype.append, … … 40 49 else 41 50 $parent.insertBefore( $, $node.nextSibling ); 42 51 } 43 } ;52 } , true ); 44 53 })(); -
_source/core/dom/range.js
457 457 }; 458 458 }, 459 459 460 moveToBookmark : function( bookmark )460 moveToBookmark : function( bookmark, isPreserve ) 461 461 { 462 // Set the range start at the bookmark start node position. 463 this.setStartBefore( bookmark.startNode ); 462 var st = bookmark.startNode , ed = bookmark.endNode; 464 463 465 // Remove it, because it may interfere in the setEndBefore call. 466 bookmark.startNode.remove(); 464 // Set the range start at the bookmark start node position. 465 this.setStartBefore( typeof st === 'string' ? ( st = this.document 466 .getById( st ) ) : st ); 467 467 468 // Set the range end at the bookmark end node position, or simply 469 // collapse it if it is not available. 470 var endNode = bookmark.endNode; 471 if ( endNode ) 472 { 473 this.setEndBefore( endNode ); 474 endNode.remove(); 475 } 476 else 477 this.collapse( true ); 468 // Remove it, because it may interfere in the setEndBefore call. 469 if(!isPreserve) 470 st.remove(); 471 472 // Set the range end at the bookmark end node position, or simply 473 // collapse it if it is not available. 474 if ( ed ) 475 { 476 this.setEndBefore( typeof ed === 'string' ? 477 ( ed = this.document.getById( ed ) ) 478 : ed ); 479 if(!isPreserve) 480 ed.remove(); 481 } 482 else 483 this.collapse( true ); 478 484 }, 479 485 480 486 getCommonAncestor : function( includeSelf ) … … 994 1000 995 1001 /** 996 1002 * Inserts a node at the start of the range. The range will be expanded 997 * the contain the node. 1003 * to contain the node. 1004 * @param {CKEDITOR.dom.node} 998 1005 */ 999 1006 insertNode : function( node ) 1000 1007 { … … 1002 1009 1003 1010 var startContainer = this.startContainer; 1004 1011 var startOffset = this.startOffset; 1012 var isDomFrag = false , firstChild , childCounts = 0; 1005 1013 1014 // Inserted node might be {@link CKEDITOR.dom.documentFragment} 1015 if ( node.type === CKEDITOR.NODE_DOCUMENT_FRAGMENT ) 1016 { 1017 isDomFrag = true; 1018 firstChild = node.getChild( 0 ); 1019 childCounts = node.getChildCount(); 1020 } 1021 1006 1022 var nextNode = startContainer.getChild( startOffset ); 1007 1023 1008 1024 if ( nextNode ) … … 1011 1027 startContainer.append( node ); 1012 1028 1013 1029 // Check if we need to update the end boundary. 1014 if ( node.getParent().equals( this.endContainer ) ) 1030 if ( isDomFrag && firstChild.getParent().equals( this.endContainer ) ) 1031 { 1032 this.endOffset += childCounts; 1033 // Expand the range to embrace the new nodes. 1034 this.setStartBefore( firstChild ); 1035 } 1036 else if ( node.getParent().equals( this.endContainer ) ) 1015 1037 this.endOffset++; 1016 1038 1017 1039 // Expand the range to embrace the new node. -
_source/plugins/selection/plugin.js
83 83 { 84 84 init : function( editor, pluginPath ) 85 85 { 86 // Listen to non-user generated selection, e.g. selection changes produced by commands. 87 editor.on( 'afterCommandExec' , checkSelectionChangeTimeout , 88 editor ); 86 89 editor.on( 'contentDom', function() 87 90 { 88 91 if ( CKEDITOR.env.ie ) -
_source/plugins/styles/plugin.js
5 5 6 6 CKEDITOR.plugins.add( 'styles', 7 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 ) 8 requires : [ 'selection' ], 9 beforeInit : function( editor, pluginPath ) 37 10 { 38 // Create the callbacks array. 39 styleStateChangeCallbacks = this._.styleStateChangeCallbacks = []; 11 12 var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 }; 13 var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 }; 40 14 41 // Attach to the selectionChange event, so we can check the styles at42 // 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 that51 // 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 current58 // state to it.59 callback.fn.call( this, currentState );60 61 // Save the current state, so it can be compared next62 // time.63 callback.state !== currentState;64 }65 }66 });67 }68 69 // Save the callback info, so it can be checked on the next occurence of70 // selectionChange.71 styleStateChangeCallbacks.push( { style : style, fn : callback } );72 };73 74 CKEDITOR.STYLE_BLOCK = 1;75 CKEDITOR.STYLE_INLINE = 2;76 CKEDITOR.STYLE_OBJECT = 3;77 78 (function()79 {80 var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };81 var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };82 83 15 CKEDITOR.style = function( styleDefinition ) 84 16 { 85 17 var element = this.element = ( styleDefinition.element || '*' ).toLowerCase(); … … 100 32 101 33 CKEDITOR.style.prototype = 102 34 { 103 apply : function( document ) 104 { 35 /** 36 * @param {CKEDITOR.dom.document} document The target document the style applies to. 37 * @param {Boolean} isReverse Whether it's a reverse apply (remove). 38 */ 39 apply : function( document , isReverse) 40 { 105 41 // Get all ranges from the selection. 106 42 var selection = document.getSelection(); 107 43 var ranges = selection.getRanges(); 108 44 var styleFuncName = isReverse? 'removeFromRange':'applyToRange'; 109 45 // Apply the style to the ranges. 110 46 for ( var i = 0 ; i < ranges.length ; i++ ) 111 this.applyToRange( ranges[ i ] ); 112 47 { 48 this[styleFuncName].call( this, ranges[i]); 49 } 113 50 // Select the ranges again. 114 51 selection.selectRanges( ranges ); 115 52 }, 53 54 reverse: function (document) { 55 this.apply(document, true); 56 }, 116 57 117 58 applyToRange : function( range ) 118 59 { … … 123 64 applyBlockStyle 124 65 : null ).call( this, range ); 125 66 }, 67 68 removeFromRange: function (range) { 69 70 return (this.type == CKEDITOR.STYLE_INLINE ? removeInlineStyle : 71 this.type == CKEDITOR.STYLE_BLOCK ? removeBlockStyle : 72 null).call(this, range); 73 }, 74 75 /** 76 * Remove all related attributes, styles on the element, or even intrinsic semantics(the element itself) which related with this style 77 * @param {CKEditor.dom.element} element The target element 78 */ 79 removeFromElement: function(element) 80 { 81 if(element.$.nodeType !== 1||element.getName() !== this.element) 82 return; 83 var def = this._.definition; 84 var attribs = def.attributes; 85 var styles = def.styles; 86 87 for (var attName in attribs) 88 { 89 // The 'class' element value must match (#1318). 90 if (attName == 'class' && element.getAttribute('class') != attribs[attName]) 91 continue; 92 93 element.removeAttribute(attName); 94 } 95 96 for (var styleName in styles) 97 { 98 element.removeStyle(styleName); 99 } 100 101 removeNoAttribsElement(element); 102 }, 103 104 /** 105 * Remove related style from all the childs of this element 106 * @param {CKEditor.dom.element} element The target element 107 * @see CKEditor.style::removeFromElement 108 */ 109 removeFromAllChilds : function( element ) 110 { 111 var innerElements = element.getElementsByTag( this.element ); 112 for ( var i = innerElements.count() ; --i >= 0 ; ) 113 { 114 var innerElement = innerElements.getItem( i ); 115 this.removeFromElement(innerElements); 116 } 117 }, 126 118 127 119 /** 128 120 * Get the style state inside an element path. Returns "true" if the … … 153 145 return false; 154 146 }, 155 147 156 // Checks if an element, or any of its attributes, is removable by the 157 // current style definition. 148 /** 149 * @param {CKEDITOR.dom.element} element The target element to check. 150 * @param {Boolean} fullMatch Whether require the attribute and styles are fully matched. 151 */ 158 152 checkElementRemovable : function( element, fullMatch ) 159 153 { 160 154 if ( !element || element.getName() != this.element ) … … 359 353 360 354 // Here we do some cleanup, removing all duplicated 361 355 // elements from the style element. 362 removeFromElement( this,styleNode );356 this.removeFromAllChilds( styleNode ); 363 357 364 358 // Insert it into the range position (it is collapsed after 365 359 // extractContents. … … 384 378 } 385 379 } 386 380 387 // 381 // this._FixBookmarkStart( startNode ); 388 382 389 383 range.moveToBookmark( bookmark ); 390 384 }; 385 386 /** 387 * Remove the style from the specified {@param range}. 388 * @param {CKEDITOR.dom.range} range The range to be processed. 389 */ 390 function removeInlineStyle(range) { 391 392 var 393 self = this, 394 //bookmark start and end boundaries 395 bookmark, 396 startNode, endNode, 397 walker, 398 //the topmost parent node which conflicts with this style removing 399 matchedTopParent; 391 400 401 var isCollapsed = range.collapsed, 402 // A placeholder for cheat with collapsed range 403 marker; 404 405 // Expand the range, if inside inline element boundaries. 406 if(!isCollapsed) 407 range.enlarge( CKEDITOR.ENLARGE_ELEMENT ); 408 409 //Cheat by insert a placeholder node which make the range no longer collapsed 410 else 411 { 412 marker = editor.document.createText( ' ' ); 413 range.insertNode(marker); 414 range.setStartBefore(marker); 415 range.setEndAfter(marker); 416 } 417 418 // Bookmark the range, bookmark nodes are used to establish boundaries. 419 bookmark = range.createBookmark(), 420 startNode = bookmark.startNode, 421 endNode = bookmark.endNode; 422 423 if ( matchedTopParent = checkPath( startNode , CKEDITOR.tools.bind( 424 this.checkElementRemovable , this ) ) ) 425 branch( startNode , matchedTopParent , true ); 426 if ( matchedTopParent = checkPath( endNode , CKEDITOR.tools.bind( 427 this.checkElementRemovable , this ) ) ) 428 branch( endNode , matchedTopParent , true ); 429 430 walker = new CKEDITOR.dom.domWalker( startNode ); 431 walker.forward( function ( walkerEvt ) 432 { 433 var currentNode = walkerEvt.data.from; 434 if ( currentNode.equals( startNode ) ) 435 return; 436 else if ( currentNode.equals( endNode ) ) 437 this.stop(); 438 else 439 self.removeFromElement( currentNode ); 440 } ); 441 442 if ( isCollapsed ) // remove the marker node 443 { 444 marker.remove(); 445 } 446 else 447 { 448 if ( !CKEDITOR.env.ie ) // normalize text nodes for non-IE 449 { 450 var frag; 451 range.moveToBookmark( bookmark , true ); 452 frag = range.extractContents(); 453 frag.$.normalize(); 454 range.insertNode( frag ); 455 } 456 } 457 458 //TODO: Anchor range boundaries inside element if possible 459 460 range.moveToBookmark(bookmark); 461 } 392 462 var applyBlockStyle = function( range ) 393 463 { 394 464 }; 395 396 // Removes a style from inside an element. 397 var removeFromElement = function( style, element ) 465 466 /** 467 * Create a new branch of the dom tree which rooted by {@param parentNode} and start from the {@param boundaryNode}, after then the {@param boundaryNode} would be placed between these two branches. 468 * For example in the following dom tree, if we specified '<span/>' as the boundaryNode, 469 * <pre> 470 * <b>This <i>is some<span /> sample</i> test text</b> 471 * </pre> 472 *The dom tree will end up with: 473 *<pre> 474 * <b>This <i>is some</i><span /><i> sample</i> test text</b> <!-- (If parent = <i>) --> 475 * <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b> <!-- (If parent = <b>) --> 476 *<pre> 477 * @param {CKEDITOR.dom.node} boundaryNode The left-most node which used as boundary of this branch. 478 * @param {CKEDITOR.dom.node} parentNode The first node which will be the root of this branch. 479 * @see FCKDomTools.BreakParent The v2 similiar method. 480 */ 481 function branch ( boundaryNode, parentNode ) 398 482 { 399 var def = style._.definition;400 var attribs = def.attributes;401 var styles = def.styles;402 483 403 var innerElements = element.getElementsByTag( style.element );484 var range = new CKEDITOR.dom.range( editor.document ); 404 485 405 for ( var i = innerElements.count() ; --i >= 0 ; ) 406 { 407 var innerElement = innerElements.getItem( i ); 486 // We'll be extracting part of this boundaryNode, so let's use our 487 // range to get the correct piece. 488 range.setStartAfter( boundaryNode ); 489 range.setEndAfter( parentNode ); 408 490 409 for ( var attName in attribs ) 410 { 411 // The 'class' element value must match (#1318). 412 if ( attName == 'class' && innerElement.getAttribute( 'class' ) != attribs[ attName ] ) 413 continue; 491 // Extract it, range should be collapsed after the first branch 492 var docFrag = range.extractContents(); 414 493 415 innerElement.removeAttribute( attName );416 }494 // Place the boundary node right after first branch 495 range.insertNode( boundaryNode.remove() ); 417 496 418 for ( var styleName in styles ) 497 // Insert the second branch in following 498 docFrag.insertAfterNode( boundaryNode ); 499 } 500 501 502 /** 503 * Reversely seaching on the element path which started by {@param pathStartNode}, return the first path element which satisfy {@param testFunction}. 504 * @param {CKEditor.dom.node} pathStartNode 505 * @param {Functoin} testFunction The function to test whether the element is the root of branch. 506 * @param {Boolean} skipBlock Whether ignore all the block level elements included in this path. 507 */ 508 function checkPath(pathStartNode, testFunction, skipBlock) { 509 510 // Let's start checking the start boundary. 511 var path = new CKEDITOR.dom.elementPath( pathStartNode ) , currentElements = path.elements; 512 var currentElement; 513 var i = skipBlock ? CKEDITOR.tools.indexOf( currentElements , path.block ) - 1 514 : currentElements.length - 1; 515 516 for ( ; i > 0 ; i-- ) // skip the beginning node 517 { 518 currentElement = currentElements[ i ]; 519 520 if ( testFunction( currentElement ) ) 419 521 { 420 innerElement.removeStyle( styleName );522 return currentElement; 421 523 } 422 423 removeNoAttribsElement( innerElement );424 524 } 425 525 }; 526 426 527 427 // If the element has no more attributes, remove it. 428 var removeNoAttribsElement = function( element ) 528 529 530 /** 531 * If no more attributes remained in the element, remove it, but leaving its 532 * children. 533 * 534 * @param {CKEDITOR.dom.element} 535 * The target element to remove. 536 */ 537 function removeNoAttribsElement( element ) 429 538 { 430 539 // If no more attributes remained in the element, remove it, 431 540 // leaving its children. 432 541 if ( !element.hasAttributes() ) 433 542 { 434 // Removing elements may open points where merging is possible,435 // so let's cache the first and last nodes for later checking.436 var firstChild = element.getFirst();437 var lastChild = element.getLast();438 439 543 element.remove( true ); 440 441 if ( firstChild )442 {443 // Check the cached nodes for merging.444 mergeSiblings( firstChild );445 446 if ( lastChild && !firstChild.equals( lastChild ) )447 mergeSiblings( lastChild );448 }449 544 } 450 545 }; 451 546 … … 586 681 // Save the created element. It will be reused on future calls. 587 682 return ( style._.element = el ); 588 683 }; 589 })(); 684 } 685 }); 590 686 687 /** 688 * Registers a function to be called whenever a style changes its state in the 689 * editing area. The current state is passed to the function. The possible 690 * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}. 691 * @param {CKEDITOR.style} The style to be watched. 692 * @param {Function} The function to be called when the style state changes. 693 * @example 694 * // Create a style object for the <b> element. 695 * var style = new CKEDITOR.style( { element : 'b' } ); 696 * var editor = CKEDITOR.instances.editor1; 697 * editor.attachStyleStateChange( style, function( state ) 698 * { 699 * if ( state == CKEDITOR.TRISTATE_ON ) 700 * alert( 'The current state for the B element is ON' ); 701 * else 702 * alert( 'The current state for the B element is OFF' ); 703 * }); 704 */ 705 CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback ) 706 { 707 // Try to get the list of attached callbacks. 708 var styleStateChangeCallbacks = this._.styleStateChangeCallbacks; 709 710 // If it doesn't exist, it means this is the first call. So, let's create 711 // all the structure to manage the style checks and the callback calls. 712 if ( !styleStateChangeCallbacks ) 713 { 714 // Create the callbacks array. 715 styleStateChangeCallbacks = this._.styleStateChangeCallbacks = []; 716 717 // Attach to the selectionChange event, so we can check the styles at 718 // that point. 719 this.on( 'selectionChange', function( ev ) 720 { 721 // Loop throw all registered callbacks. 722 for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ ) 723 { 724 var callback = styleStateChangeCallbacks[ i ]; 725 726 // Check the current state for the style defined for that 727 // callback. 728 var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF; 729 730 // If the state changed since the last check. 731 if ( callback.state !== currentState ) 732 { 733 // Call the callback function, passing the current 734 // state to it. 735 callback.fn.call( this, currentState ); 736 737 // Save the current state, so it can be compared next 738 // time. 739 callback.state !== currentState; 740 } 741 } 742 }); 743 } 744 745 // Save the callback info, so it can be checked on the next occurence of 746 // selectionChange. 747 styleStateChangeCallbacks.push( { style : style, fn : callback } ); 748 }; 749 750 CKEDITOR.STYLE_BLOCK = 1; 751 CKEDITOR.STYLE_INLINE = 2; 752 CKEDITOR.STYLE_OBJECT = 3; 753 754 591 755 CKEDITOR.styleCommand = function( style ) 592 756 { 593 757 this.style = style; … … 600 764 var doc = editor.document; 601 765 602 766 if ( doc ) 603 this.style.apply( doc ); 767 { 768 if(this.state === CKEDITOR.TRISTATE_OFF) 769 { 770 this.style.apply( doc ); 771 } 772 else if(this.state === CKEDITOR.TRISTATE_ON) 773 { 774 this.style.reverse(doc); 775 } 776 } 604 777 605 778 return !!doc; 606 779 };