Index: /CKEditor/branches/features/paste/_source/core/dtd.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/dtd.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/dtd.js (revision 4674) @@ -59,5 +59,6 @@ V = {html:1}; - var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}; + var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}, + nonEmptyInline = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 }; return /** @lends CKEDITOR.dtd */ { @@ -74,4 +75,12 @@ */ $block : block, + + + /** + * List of non-empty inline elements, like "b" or "a". + * @type Object + * @example + */ + $nonEmptyInline : nonEmptyInline, /** Index: /CKEditor/branches/features/paste/_source/core/htmlparser/comment.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/htmlparser/comment.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/htmlparser/comment.js (revision 4674) @@ -1,3 +1,3 @@ -/* +/* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license @@ -50,4 +50,5 @@ if ( typeof comment != 'string' ) { + comment.parent = this.parent; comment.writeHtml( writer, filter ); return; Index: /CKEditor/branches/features/paste/_source/core/htmlparser/element.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/htmlparser/element.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/htmlparser/element.js (revision 4674) @@ -1,3 +1,3 @@ -/* +/* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license @@ -102,16 +102,25 @@ var attributes = this.attributes; - // The "_cke_replacedata" indicates that this element is replacing - // a data snippet, which should be outputted as is. - if ( attributes._cke_replacedata ) - { - writer.write( attributes._cke_replacedata ); - return; - } - // Ignore cke: prefixes when writing HTML. var element = this, writeName = element.name, - a, value; + a, newAttrName, value; + + var isChildrenFiltered; + + /** + * Providing an option for bottom-up filtering order ( element + * children to be pre-filtered before the element itself ). + */ + element.filterChildren = function() + { + if( !isChildrenFiltered ) + { + var writer = new CKEDITOR.htmlParser.basicWriter(); + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.call( element, writer, filter ); + element.children = new CKEDITOR.htmlParser.fragment.fromHtml( writer.getHtml() ).children; + isChildrenFiltered = 1; + } + }; if ( filter ) @@ -126,4 +135,6 @@ if ( !( element = filter.onElement( element ) ) ) return; + + element.parent = this.parent; if ( element.name == writeName ) @@ -139,7 +150,10 @@ writeName = element.name; - if ( !writeName ) // Send children. + + // This indicate that the element has been dropped by + // filter but not the children. + if ( !writeName ) { - CKEDITOR.htmlParser.fragment.prototype.writeHtml.apply( element, arguments ); + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); return; } @@ -154,39 +168,53 @@ writer.openTag( writeName, attributes ); - if ( writer.sortAttributes ) - { - // Copy all attributes to an array. - var attribsArray = []; + // Copy all attributes to an array. + var attribsArray = []; + // Iterate over the attributes twice since filters may alter + // other attributes. + for( var i = 0 ; i < 2; i++ ) + { for ( a in attributes ) { + newAttrName = a; value = attributes[ a ]; - - if ( filter && ( !( a = filter.onAttributeName( a ) ) || ( value = filter.onAttribute( element, a, value ) ) === false ) ) - continue; - - attribsArray.push( [ a, value ] ); + if( i == 1 ) + attribsArray.push( [ a, value ] ); + else if ( filter ) + { + while ( true ) + { + if ( !( newAttrName = filter.onAttributeName( a ) ) ) + { + delete attributes[ a ]; + break; + } + else if( newAttrName != a ) + { + delete attributes[ a ]; + a = newAttrName; + continue; + } + else + break; + } + if( newAttrName ) + { + if( ( value = filter.onAttribute( element, newAttrName, value ) ) === false ) + delete attributes[ newAttrName ]; + else + attributes [ newAttrName ] = value; + } + } } - - // Sort the attributes by name. + } + // Sort the attributes by name. + if ( writer.sortAttributes ) attribsArray.sort( sortAttribs ); - // Send the attributes. - for ( var i = 0, len = attribsArray.length ; i < len ; i++ ) - { - var attrib = attribsArray[ i ]; - writer.attribute( attrib[0], attrib[1] ); - } - } - else - { - for ( a in attributes ) - { - value = attributes[ a ]; - - if ( filter && ( !( a = filter.onAttributeName( a ) ) || ( value = filter.onAttribute( element, a, value ) ) === false ) ) - continue; - - writer.attribute( a, value ); - } + // Send the attributes. + for ( i = 0, len = attribsArray.length ; i < len ; i++ ) + { + var attrib = attribsArray[ i ]; + writer.attribute( attrib[0], attrib[1] ); } @@ -196,10 +224,15 @@ if ( !element.isEmpty ) { - // Send children. - CKEDITOR.htmlParser.fragment.prototype.writeHtml.apply( element, arguments ); - + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); // Close the element. writer.closeTag( writeName ); } + }, + + writeChildrenHtml : function( writer, filter ) + { + // Send children. + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.apply( this, arguments ); + } }; Index: /CKEditor/branches/features/paste/_source/core/htmlparser/filter.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/htmlparser/filter.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/htmlparser/filter.js (revision 4674) @@ -1,2 +1,4 @@ + + /* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. @@ -75,8 +77,8 @@ // well as those set to the generic $ name. So, add both to an // array and process them in a small loop. - var filters = [ this._.elements[ element.name ], this._.elements.$ ], + var filters = [ this._.elements[ '^' ], this._.elements[ element.name ], this._.elements.$ ], filter, ret; - for ( var i = 0 ; i < 2 ; i++ ) + for ( var i = 0 ; i < 3 ; i++ ) { filter = filters[ i ]; @@ -90,4 +92,8 @@ if ( ret && ret != element ) return this.onNode( ret ); + + // The none-root element has been dismissed by one of the filters. + if( element.parent && !element.name ) + break; } } @@ -122,4 +128,12 @@ return value; + }, + + clone : function() + { + var clone = new CKEDITOR.htmlParser.filter(); + // Shallow copy all the rules. + clone._ = CKEDITOR.tools.clone( this._ ); + return clone; } } @@ -155,6 +169,9 @@ { var item = items[ j ]; - item.pri = priority; - list.splice( i, 0, item ); + if( item ) + { + item.pri = priority; + list.splice( i, 0, item ); + } } } Index: /CKEditor/branches/features/paste/_source/core/htmlparser/fragment.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/htmlparser/fragment.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/htmlparser/fragment.js (revision 4674) @@ -357,4 +357,5 @@ parser.onComment = function( comment ) { + checkPending(); currentNode.add( new CKEDITOR.htmlParser.comment( comment ) ); }; @@ -438,9 +439,28 @@ * alert( writer.getHtml() ); "<p><b>Example</b></p>" */ - writeHtml : function( writer, filter ) - { - for ( var i = 0, len = this.children.length ; i < len ; i++ ) + writeHtml : function( writer, filter, skipSelf ) + { + var isChildrenFiltered; + this.filterChildren = function() + { + var writer = new CKEDITOR.htmlParser.basicWriter(); + this.writeChildrenHtml.call( this, writer, filter, true ); + var html = writer.getHtml(); + this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children; + isChildrenFiltered = 1; + }; + + if ( !filter.onElement( this ) ) + return; + + this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter ); + }, + + writeChildrenHtml : function( writer, filter ) + { + for ( var i = 0 ; i < this.children.length ; i++ ) this.children[i].writeHtml( writer, filter ); } + }; })(); Index: /CKEditor/branches/features/paste/_source/core/scriptloader.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/scriptloader.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/scriptloader.js (revision 4674) @@ -1,3 +1,3 @@ -/* +/* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license @@ -52,5 +52,5 @@ * }); */ - load : function( scriptUrl, callback, scope, noCheck ) + load : function( scriptUrl, callback, scope, noCheck, showBusy ) { var isString = ( typeof scriptUrl == 'string' ); @@ -88,5 +88,8 @@ if ( --scriptCount <= 0 ) + { + showBusy && CKEDITOR.document.getDocumentElement().removeStyle( 'cursor' ); doCallback( success ); + } }; @@ -168,4 +171,5 @@ }; + showBusy && CKEDITOR.document.getDocumentElement().setStyle( 'cursor', 'wait' ); for ( var i = 0 ; i < scriptCount ; i++ ) { Index: /CKEditor/branches/features/paste/_source/core/tools.js =================================================================== --- /CKEditor/branches/features/paste/_source/core/tools.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/core/tools.js (revision 4674) @@ -1,3 +1,3 @@ -/* +/* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license @@ -64,15 +64,4 @@ var clone; - // Array. - if ( obj && ( obj instanceof Array ) ) - { - clone = []; - - for ( var i = 0 ; i < obj.length ; i++ ) - clone[ i ] = this.clone( obj[ i ] ); - - return clone; - } - // "Static" types. if ( obj === null @@ -81,5 +70,6 @@ || ( obj instanceof Number ) || ( obj instanceof Boolean ) - || ( obj instanceof Date ) ) + || ( obj instanceof Date ) + || ( obj instanceof RegExp) ) { return obj; @@ -586,5 +576,20 @@ { return new Array( times + 1 ).join( str ); - } + }, + + tryThese : function() + { + var returnValue; + for ( var i = 0, length = arguments.length; i < length; i++ ) + { + var lambda = arguments[i]; + try + { + returnValue = lambda(); + break; + } catch (e) { } + } + return returnValue; + } }; })(); Index: /CKEditor/branches/features/paste/_source/lang/en.js =================================================================== --- /CKEditor/branches/features/paste/_source/lang/en.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/lang/en.js (revision 4674) @@ -514,9 +514,7 @@ pastefromword : { + confirmCleanup : 'Your pasted content including formattings from MS-Word application, do you want to adapt it to the editor\'s format?', toolbar : 'Paste from Word', - title : 'Paste from Word', - advice : 'Please paste inside the following box using the keyboard (Ctrl+V) and hit OK.', - ignoreFontFace : 'Ignore Font Face definitions', - removeStyle : 'Remove Styles definitions' + title : 'Paste from Word' }, Index: /CKEditor/branches/features/paste/_source/plugins/clipboard/dialogs/paste.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/clipboard/dialogs/paste.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/clipboard/dialogs/paste.js (revision 4674) @@ -119,5 +119,5 @@ setTimeout( function(){ - editor.insertHtml( html ); + editor.fire( 'paste', { 'html' : html } ); }, 0 ); Index: /CKEditor/branches/features/paste/_source/plugins/clipboard/plugin.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/clipboard/plugin.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/clipboard/plugin.js (revision 4674) @@ -86,8 +86,8 @@ editor.focus(); - if ( !editor.fire( 'beforePaste' ) - && !execIECommand( editor, 'paste' ) ) - { - editor.openDialog( 'paste' ); + if ( !execIECommand( editor, 'paste' ) ) + { + editor.fire( 'pasteDialog' ); + return false; } } @@ -99,5 +99,5 @@ try { - if ( !editor.fire( 'beforePaste' ) + if ( !editor.document.getBody().fire( 'beforepaste' ) && !editor.document.$.execCommand( 'Paste', false, null ) ) { @@ -107,6 +107,9 @@ catch ( e ) { - // Open the paste dialog. - editor.openDialog( 'paste' ); + setTimeout( function() + { + editor.fire( 'pasteDialog' ); + }, 0 ); + return false; } } @@ -122,14 +125,16 @@ case CKEDITOR.SHIFT + 45 : // SHIFT+INS - var editor = this; + var editor = this, + body = editor.document.getBody(); + editor.fire( 'saveSnapshot' ); // Save before paste - if ( editor.fire( 'beforePaste' ) ) + // Simulate 'beforepaste' event for all none-IEs. + if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) ) event.cancel(); - - setTimeout( function() - { - editor.fire( 'saveSnapshot' ); // Save after paste - }, 0 ); + // Simulate 'paste' event for Opera/Firefox2. + else if( CKEDITOR.env.opera + || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + body.fire( 'paste' ); return; @@ -148,9 +153,138 @@ }; + // Allow to peek clipboard content by redirecting the + // pasting content into a temporary bin and grab the content of it. + function getClipboardData( evt, mode, callback ) { + + var doc = this.document; + + // Avoid recursions on 'paste' event for IE. + if( CKEDITOR.env.ie && doc.getById( 'cke_pastebin' ) ) + return; + + var sel = this.getSelection(), + range = new CKEDITOR.dom.range( doc ); + + // Create container to paste into + var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : 'div', doc ); + pastebin.setAttribute( 'id', 'cke_pastebin' ); + + // IE6/7 require the bin to at least contain one piece of text otherwise the + // paste is treated as unauthorized. + if( mode != 'text' && CKEDITOR.env.ie ) + pastebin.append( new CKEDITOR.dom.text( '\u00A0', doc ) ); + + doc.getBody().append( pastebin ); + + + // It's definitely a better user experience if we make the paste-bin pretty unnoticed + // by pulling it off the screen, while this hack will make the paste-bin a control type element + // and that become a selection plain later. + if( !CKEDITOR.env.ie ) + { + pastebin.setStyles( { + position : 'absolute', + left : '-1000px', + // Position the bin exactly at the position of the selected element + // to avoid any subsequent document scroll. + top : sel.getStartElement().getDocumentPosition().y + 'px', + width : '1px', + height : '1px', + overflow : 'hidden' + } ); + } + + var bms = sel.createBookmarks(); + + // Turn off design mode temporarily before give focus to the paste bin. + if ( mode == 'text' ) + { + doc.$.designMode = 'off'; + pastebin.$.focus(); + } + else + { + range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START ); + range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END ); + range.select( true ); + } + + // Wait a while and grab the pasted contents + window.setTimeout( function() { + + mode == 'text' && ( doc.$.designMode = 'on' ); + pastebin.remove(); + + // Grab the HTML contents + // We need to look for a apple style wrapper on webkit it also adds a div wrapper if you copy/paste the body of the editor. + // Remove hidden div and restore selection + var bogusSpan; + pastebin = ( CKEDITOR.env.webkit + && ( bogusSpan = pastebin.getFirst() ) + && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ? + bogusSpan : pastebin ); + + sel.selectBookmarks( bms ); + callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() ); + + }, 0 ); + }; + + // Register the plugin. CKEDITOR.plugins.add( 'clipboard', { + requires : [ 'htmldataprocessor' ], init : function( editor ) { + // The paste processor here is just a reduced copy of html data processor. + CKEDITOR.pasteProcessor = function( editor ) + { + this.editor = editor; + this.dataFilter = new CKEDITOR.htmlParser.filter(); + }; + CKEDITOR.pasteProcessor.prototype = + { + toHtml : function( data ) + { + var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, false ), + writer = new CKEDITOR.htmlParser.basicWriter(); + + fragment.writeHtml( writer, this.dataFilter ); + return writer.getHtml( true ); + } + }; + + // The very first handler which initialize the processor. + editor.on( 'paste', function( evt ) + { + // The processor is a transient instance life cycled to the + // 'paste' event since the processing rules will be added + // on demand accordingly to clipboard data flavor. + evt.data.processor = new CKEDITOR.pasteProcessor( editor ); + + }, null, null, 1 ); + + // The very last handler which insert final data into the editor at the end of the chain. + editor.on( 'paste', function( evt ) + { + var data = evt.data, + processor = data.processor; + + if ( data[ 'html' ] ) + editor.insertHtml( processor.toHtml( data[ 'html' ], false ) ); + else if ( data[ 'text' ] ) + editor.insertText( data[ 'text' ] ); + + editor.fire( 'saveSnapshot' ); // Save after inserted. + + }, null, null, 1000 ); + + editor.on( 'pasteDialog', function( evt ) + { + // Open default paste dialog. + editor.openDialog( 'paste' ); + } ); + function addButtonCommand( buttonName, commandName, command, ctxMenuOrder ) { @@ -185,4 +319,30 @@ editor.on( 'key', onKey, editor ); + if( editor.config.autoDetectPaste ) + { + var mode = editor.config.forcePasteAsPlainText ? 'text' : 'html'; + editor.on( 'contentDom', function() + { + var body = editor.document.getBody(); + body.on( ( mode == 'text' && !CKEDITOR.env.ie ) ? + 'beforepaste' : 'beforepaste', + function( evt ) + { + getClipboardData.call( editor, evt, mode, function ( data ) + { + // The very last guard to make sure the + // paste has really happened. + if ( !data ) + return; + + var dataTransfer = {}; + dataTransfer[ mode ] = data; + editor.fire( 'paste', dataTransfer ); + } ); + } ); + + } ); + } + // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) @@ -206,3 +366,15 @@ } }); + + })(); + +/** + * Whether to automatically choose the right format when pasting based on the + * detection of content text OR just leave it to the browser's default paste behavior. + * @type Boolean + * @default true + * @example + * config.autoDetectPaste = false; + */ +CKEDITOR.config.autoDetectPaste = true; Index: /CKEditor/branches/features/paste/_source/plugins/dialog/plugin.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/dialog/plugin.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/dialog/plugin.js (revision 4674) @@ -669,4 +669,5 @@ this.fireOnce( 'load', {} ); this.fire( 'show', {} ); + this._.editor.fire( 'dialogShow', this ); // Save the initial values of the dialog. @@ -732,4 +733,5 @@ { this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); // Remove the dialog's element from the root document. Index: /CKEditor/branches/features/paste/_source/plugins/fakeobjects/plugin.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/fakeobjects/plugin.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/fakeobjects/plugin.js (revision 4674) @@ -12,5 +12,6 @@ $ : function( element ) { - var realHtml = element.attributes._cke_realelement, + var attributes = element.attributes, + realHtml = attributes && attributes._cke_realelement, realFragment = realHtml && new CKEDITOR.htmlParser.fragment.fromHtml( decodeURIComponent( realHtml ) ), realElement = realFragment && realFragment.children[ 0 ]; Index: /CKEditor/branches/features/paste/_source/plugins/htmldataprocessor/plugin.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/htmldataprocessor/plugin.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/htmldataprocessor/plugin.js (revision 4674) @@ -1,3 +1,3 @@ -/* +/* Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license @@ -84,6 +84,32 @@ // Event attributes (onXYZ) must not be directly set. They can become // active in the editing area (IE|WebKit). - [ ( /^on/ ), '_cke_pa_on' ] - ] + [ ( /^on/ ), '_cke_pa_on' ], + // Remove bogus attributes. + [ ( /^cke:.*/ ), '' ] + ], + attributes : + { + // Remove empty style attribute. + 'style' : function( value ) + { + if( !value ) + return false; + } + }, + elements : + { + 'span' : function( element ) + { + var attrs = element.attributes; + for( var attr in attrs ) + { + if( attrs.hasOwnProperty( attr ) ) + { + return; + } + } + delete element.name; + } + } }; Index: /CKEditor/branches/features/paste/_source/plugins/pagebreak/plugin.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/pagebreak/plugin.js (revision 4673) +++ /CKEditor/branches/features/paste/_source/plugins/pagebreak/plugin.js (revision 4674) @@ -56,5 +56,6 @@ div : function( element ) { - var style = element.attributes.style, + var attributes = element.attributes + style = attributes && element.attributes.style, child = style && element.children.length == 1 && element.children[ 0 ], childStyle = child && ( child.name == 'span' ) && child.attributes.style; Index: Editor/branches/features/paste/_source/plugins/pastefromword/dialogs/pastefromword.js =================================================================== --- /CKEditor/branches/features/paste/_source/plugins/pastefromword/dialogs/pastefromword.js (revision 4673) +++ (revision ) @@ -1,306 +1,0 @@ -/* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.html or http://ckeditor.com/license -*/ - -CKEDITOR.dialog.add( 'pastefromword', function( editor ) -{ - return { - title : editor.lang.pastefromword.title, - minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 370 : 350, - minHeight : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 270 : 260, - htmlToLoad : '
', - cleanWord : function( editor, html, ignoreFont, removeStyles ) - { - // Remove comments [SF BUG-1481861]. - html = html.replace(/<\!--[\s\S]*?-->/g, '' ) ; - - html = html.replace(/