From f4a79a8708c52ead969e450fe8bb35270c124439 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 18 Feb 2014 12:23:26 +0100 Subject: [PATCH 01/67] Introduce selection.getSelectedHtml method. --- core/selection.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/selection.js b/core/selection.js index dc03515d7c9..123911b38ef 100644 --- a/core/selection.js +++ b/core/selection.js @@ -1637,6 +1637,33 @@ return ( cache.selectedText = text ); }, + /** + * Retrieves the HTML contained within the range. If selection + * contains multiple ranges method will return concatenation of HTMLs from ranges. + * + * var text = editor.getSelection().getSelectedText(); + * alert( text ); + * + * @since 4.4 + * @returns {String} A string of HTML within the current selection. + */ + getSelectedHtml: function() { + var nativeSel = this.getNative(); + + if ( nativeSel && nativeSel.createRange ) + return nativeSel.createRange().htmlText; + + if ( nativeSel.rangeCount > 0 ) { + var div = document.createElement( 'div' ); + + for ( var i = 0; i < nativeSel.rangeCount; i++ ) { + div.appendChild( nativeSel.getRangeAt( i ).cloneContents() ); + } + return div.innerHTML; + } + return ''; + }, + /** * Locks the selection made in the editor in order to make it possible to * manipulate it without browser interference. A locked selection is From 579188758eff694e3521b89a3e427a50a9988497 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 18 Feb 2014 15:14:47 +0100 Subject: [PATCH 02/67] Copy getRangeAtDropPosition. --- plugins/clipboard/plugin.js | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index a65b0e18109..50e5993ae50 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1179,6 +1179,45 @@ return data; } + + // Copy of getRangeAtDropPosition method from widget plugin. + // In #11219 method in widget should be removed and everything be according to DRY. + function getRangeAtDropPosition( editor, dropEvt ) { + var $evt = dropEvt.data.$, + $range, + range = editor.createRange(); + + // Make testing possible. + if ( dropEvt.data.testRange ) + return dropEvt.data.testRange; + + // Webkits. + if ( document.caretRangeFromPoint ) { + $range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY ); + range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); + range.collapse( true ); + } + // FF. + else if ( $evt.rangeParent ) { + range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); + range.collapse( true ); + } + // IEs. + else if ( document.body.createTextRange ) { + $range = editor.document.getBody().$.createTextRange(); + $range.moveToPoint( $evt.clientX, $evt.clientY ); + var id = 'cke-temp-' + ( new Date() ).getTime(); + $range.pasteHTML( '\u200b' ); + + var span = editor.document.getById( id ); + range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); + span.remove(); + } + else + return null; + + return range; + } } )(); /** From 662f9e36b84384deb53726111bb340a682106a7c Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 19 Feb 2014 11:37:13 +0100 Subject: [PATCH 03/67] Internal D&D support. --- plugins/clipboard/plugin.js | 145 ++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 8 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 50e5993ae50..ea231237548 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -497,6 +497,68 @@ } ); editable.on( 'keyup', setToolbarStates ); + + var editable = editor.editable(), + // #11123 Firefox needs to listen on document, because otherwise event won't be fired. + // #11086 IE8 cannot listen on document. + dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, + clipboard, dragRanges, dragTimestamp; + + editable.attachListener( dropTarget, 'dragstart', function( evt ) { + dragTimestamp = 'cke-' + ( new Date() ).getTime(); + clipboard = editor.getSelection().getSelectedHtml(); + dragRanges = editor.getSelection().getRanges(); + evt.data.$.dataTransfer.setData( 'text', dragTimestamp ); + } ); + + editable.attachListener( dropTarget, 'drop', function( evt ) { + if ( evt.data.$.dataTransfer.getData( 'text' ) == dragTimestamp ) { + var dropRange = editor.createRange(), + dragRange, + dropBookmark, + dragBookmarks = []; + + dropRange = getRangeAtDropPosition( editor, evt ); + + if ( dropRange ) { + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + // Create bookmarks in the correct order. + for ( var i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + }; + + var dropRangeCopy = dropRange.clone(); + dropBookmark = dropRangeCopy.createBookmark( 1 ); + + for ( var i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + }; + + // Delete content for the drag position. + for ( var i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange() + dragRange.moveToBookmark( dragBookmarks[ i ] ); + dragRange.deleteContents(); + } + + // Paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteEvents( 'html', clipboard ); + + editor.fire( 'unlockSnapshot' ); + } + } + evt.data.preventDefault(); + } ); + } // Create object representing Cut or Copy commands. @@ -1184,7 +1246,10 @@ // In #11219 method in widget should be removed and everything be according to DRY. function getRangeAtDropPosition( editor, dropEvt ) { var $evt = dropEvt.data.$, + x = $evt.clientX, + y = $evt.clientY, $range, + defaultRange = editor.getSelection().getRanges()[ 0 ], range = editor.createRange(); // Make testing possible. @@ -1193,7 +1258,7 @@ // Webkits. if ( document.caretRangeFromPoint ) { - $range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY ); + $range = editor.document.$.caretRangeFromPoint( x, y ); range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); range.collapse( true ); } @@ -1202,22 +1267,86 @@ range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); range.collapse( true ); } - // IEs. + // IEs 9+. + else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 ) + // On IE 9+ range by default is where we expected it. + return defaultRange; + // IE 8. else if ( document.body.createTextRange ) { $range = editor.document.getBody().$.createTextRange(); - $range.moveToPoint( $evt.clientX, $evt.clientY ); - var id = 'cke-temp-' + ( new Date() ).getTime(); - $range.pasteHTML( '\u200b' ); + try { + var sucess = false; + + for ( var i = 0; i < 20 && !sucess; i++ ) { + if ( !sucess ) { + try { + $range.moveToPoint( x, y - i ); + sucess = true; + } catch ( err ) { + } + } + if ( !sucess ) { + try { + $range.moveToPoint( x, y + i ); + sucess = true; + } catch ( err ) { + } + } + }; - var span = editor.document.getById( id ); - range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); - span.remove(); + if ( sucess ) { + var id = 'cke-temp-' + ( new Date() ).getTime(); + $range.pasteHTML( '\u200b' ); + + var span = editor.document.getById( id ); + range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); + span.remove(); + } else { + // Try to use elementFromPoint. + var $element = editor.document.$.elementFromPoint( x, y ), + element = new CKEDITOR.dom.element( $element ), + rect; + + if ( !element.equals( editor.editable() ) ) { + rect = element.getClientRect(); + + if ( x < rect.left ) { + range.setStartAt( element, CKEDITOR.POSITION_AFTER_START ); + range.collapse( true ); + } else { + range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END ); + range.collapse( true ); + } + } + // elementFromPoint if editable try with defaultRange. + else if ( defaultRange && defaultRange.startContainer && !defaultRange. + startContainer.equals( editor.editable() ) ) { + return defaultRange; + } else + return null; + + } + } catch ( err ) { + return null; + } } else return null; return range; } + + function rangeBefore( firstRange, secondRange ) { + if ( firstRange.startContainer.equals( secondRange.startContainer ) && + firstRange.startOffset < secondRange.startOffset ) + return true; + + if ( firstRange.startContainer.getParent().equals( secondRange.startContainer ) && + firstRange.startContainer.getIndex() < secondRange.startOffset ) + return true; + + return false; + } } )(); /** From 4ed25522636d5795e6e435a6720a6205767e04cf Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 28 Mar 2014 13:51:21 +0100 Subject: [PATCH 04/67] IE8&9 fix. --- plugins/clipboard/plugin.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index ea231237548..515cdc57839 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -524,6 +524,25 @@ editor.fire( 'saveSnapshot' ); editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + // Fix IE 8 & 9 splitted node on drop + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && + dropRange.startContainer.type == 1 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ); + + var offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); + } + + dropRange.startContainer = nodeBefore; + dropRange.startOffset = offset; + } + // Create bookmarks in the correct order. for ( var i = 0; i < dragRanges.length; i++ ) { dragRange = dragRanges[ i ]; From a0e97624d7825c3834b725de785e8d8db9ab00e6 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 28 Mar 2014 14:09:35 +0100 Subject: [PATCH 05/67] Drop from external source. --- plugins/clipboard/plugin.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 515cdc57839..d8e0d87f2a5 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1,4 +1,4 @@ -/** +/** * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ @@ -575,6 +575,29 @@ editor.fire( 'unlockSnapshot' ); } } + // Drop from external source. + else { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) + editor.focus(); + + var dropRange = getRangeAtDropPosition( editor, evt ); + + if ( dropRange ) { + // Paste content into the drop position. + dropRange.select(); + var dataTransfer = evt.data.$.dataTransfer, + data; + try { + data = dataTransfer.getData( 'text/html' ) + } catch ( err ) { + } + + if ( data ) + firePasteEvents( 'html', data ); + else + firePasteEvents( 'text', dataTransfer.getData( 'Text' ) ); + } + } evt.data.preventDefault(); } ); @@ -1268,7 +1291,7 @@ x = $evt.clientX, y = $evt.clientY, $range, - defaultRange = editor.getSelection().getRanges()[ 0 ], + defaultRange = editor.getSelection( true ).getRanges()[ 0 ], range = editor.createRange(); // Make testing possible. From c368db5d78f7ae3770571140e92bd343807bb7e4 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 7 Apr 2014 16:20:26 +0200 Subject: [PATCH 06/67] Encode dropped HTML in IE. --- plugins/clipboard/plugin.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index d8e0d87f2a5..26ba85e84c6 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -594,8 +594,10 @@ if ( data ) firePasteEvents( 'html', data ); - else - firePasteEvents( 'text', dataTransfer.getData( 'Text' ) ); + else { + data = dataTransfer.getData( 'Text' ); + firePasteEvents( 'text', CKEDITOR.tools.htmlEncode( data ) ); + } } } evt.data.preventDefault(); From 79b4e90bded86f3a880532f7c9537b4647f17e6b Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 7 Apr 2014 10:11:25 +0200 Subject: [PATCH 07/67] Added timeout to the drop event. --- plugins/clipboard/plugin.js | 112 +++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 26ba85e84c6..e2df34edac9 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -513,67 +513,71 @@ editable.attachListener( dropTarget, 'drop', function( evt ) { if ( evt.data.$.dataTransfer.getData( 'text' ) == dragTimestamp ) { - var dropRange = editor.createRange(), - dragRange, - dropBookmark, - dragBookmarks = []; - - dropRange = getRangeAtDropPosition( editor, evt ); + var dropRange = getRangeAtDropPosition( editor, evt ); if ( dropRange ) { - editor.fire( 'saveSnapshot' ); - editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - - // Fix IE 8 & 9 splitted node on drop - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && - dropRange.startContainer.type == 1 && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { - var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ); - - var offset = nodeBefore.getLength(); - - if ( nodeAfter ) { - nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); - nodeAfter.remove(); + // Execute D&D with a timeout because otherwise selection after + // drop is in the drag position instead of drop position. + setTimeout( function() { + var dragRange, + dropBookmark, + dragBookmarks = []; + + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + // Fix IE 8 & 9 splitted node on drop + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && + dropRange.startContainer.type == 1 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ); + + var offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); + } + + dropRange.startContainer = nodeBefore; + dropRange.startOffset = offset; } - dropRange.startContainer = nodeBefore; - dropRange.startOffset = offset; - } - - // Create bookmarks in the correct order. - for ( var i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - }; - - var dropRangeCopy = dropRange.clone(); - dropBookmark = dropRangeCopy.createBookmark( 1 ); - - for ( var i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - }; - - // Delete content for the drag position. - for ( var i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange() - dragRange.moveToBookmark( dragBookmarks[ i ] ); - dragRange.deleteContents(); - } + // Create bookmarks in the correct order. + for ( var i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + }; + + var dropRangeCopy = dropRange.clone(); + dropBookmark = dropRangeCopy.createBookmark( 1 ); + + for ( var i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + }; + + // Delete content for the drag position. + for ( var i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange() + dragRange.moveToBookmark( dragBookmarks[ i ] ); + dragRange.deleteContents(); + } - // Paste content into the drop position. - dropRange = editor.createRange(); - dropRange.moveToBookmark( dropBookmark ); - dropRange.select(); - firePasteEvents( 'html', clipboard ); + // Paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteEvents( 'html', clipboard ); - editor.fire( 'unlockSnapshot' ); + editor.fire( 'unlockSnapshot' ); + }, 0 ); } + } // Drop from external source. else { From 6edb4b15acfb992305c6ddb4f1a9379c5e6a8a1f Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Apr 2014 17:07:10 +0200 Subject: [PATCH 08/67] Checking element.getName in getRangeAtDropPosition. --- plugins/clipboard/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index e2df34edac9..2e972c331ff 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1355,7 +1355,7 @@ element = new CKEDITOR.dom.element( $element ), rect; - if ( !element.equals( editor.editable() ) ) { + if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) { rect = element.getClientRect(); if ( x < rect.left ) { From 7004ab2bd8f99034906cf8a4a1801a6f83553a67 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 4 Apr 2014 17:07:12 +0200 Subject: [PATCH 09/67] Added 'paste' to the CKCONSOLE. --- plugins/clipboard/dev/console.js | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 plugins/clipboard/dev/console.js diff --git a/plugins/clipboard/dev/console.js b/plugins/clipboard/dev/console.js new file mode 100644 index 00000000000..55f3f8da428 --- /dev/null +++ b/plugins/clipboard/dev/console.js @@ -0,0 +1,47 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +'use strict'; + +( function() { + var pasteType, pasteValue; + + CKCONSOLE.add( 'paste', { + panels: [ + { + type: 'box', + content: + '
    ' + + '
  • type:
  • ' + + '
  • value:
  • ' + + '
', + + refresh: function( editor ) { + return { + header: 'Paste', + type: pasteType, + value: pasteValue + }; + }, + + refreshOn: function( editor, refresh ) { + editor.on( 'paste', function( evt ) { + pasteType = evt.data.type; + pasteValue = CKEDITOR.tools.htmlEncode( evt.data.dataValue ); + refresh(); + } ); + } + }, + { + type: 'log', + on: function( editor, log, logFn ) { + editor.on( 'paste', function( evt ) { + logFn( 'paste; type:' + evt.data.type )(); + } ); + } + } + ] + } ); +} )(); \ No newline at end of file From 77f0405b4d59a72bc5ddd41aa72feffddc204329 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 8 Apr 2014 10:50:53 +0200 Subject: [PATCH 10/67] JS Lint. --- plugins/clipboard/plugin.js | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 2e972c331ff..e3f711f9736 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -498,10 +498,9 @@ editable.on( 'keyup', setToolbarStates ); - var editable = editor.editable(), - // #11123 Firefox needs to listen on document, because otherwise event won't be fired. - // #11086 IE8 cannot listen on document. - dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, + // #11123 Firefox needs to listen on document, because otherwise event won't be fired. + // #11086 IE8 cannot listen on document. + var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, clipboard, dragRanges, dragTimestamp; editable.attachListener( dropTarget, 'dragstart', function( evt ) { @@ -512,17 +511,19 @@ } ); editable.attachListener( dropTarget, 'drop', function( evt ) { + var dragRange, + dropRange, + dropBookmark, + dragBookmarks = [], + i; + if ( evt.data.$.dataTransfer.getData( 'text' ) == dragTimestamp ) { - var dropRange = getRangeAtDropPosition( editor, evt ); + dropRange = getRangeAtDropPosition( editor, evt ); if ( dropRange ) { // Execute D&D with a timeout because otherwise selection after // drop is in the drag position instead of drop position. setTimeout( function() { - var dragRange, - dropBookmark, - dragBookmarks = []; - editor.fire( 'saveSnapshot' ); editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); @@ -532,9 +533,8 @@ dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ); - - var offset = nodeBefore.getLength(); + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), + offset = nodeBefore.getLength(); if ( nodeAfter ) { nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); @@ -546,24 +546,24 @@ } // Create bookmarks in the correct order. - for ( var i = 0; i < dragRanges.length; i++ ) { + for ( i = 0; i < dragRanges.length; i++ ) { dragRange = dragRanges[ i ]; if ( !rangeBefore( dragRange, dropRange ) ) dragBookmarks.push( dragRange.createBookmark( 1 ) ); - }; + } var dropRangeCopy = dropRange.clone(); dropBookmark = dropRangeCopy.createBookmark( 1 ); - for ( var i = 0; i < dragRanges.length; i++ ) { + for ( i = 0; i < dragRanges.length; i++ ) { dragRange = dragRanges[ i ]; if ( rangeBefore( dragRange, dropRange ) ) dragBookmarks.push( dragRange.createBookmark( 1 ) ); - }; + } // Delete content for the drag position. - for ( var i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange() + for ( i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange(); dragRange.moveToBookmark( dragBookmarks[ i ] ); dragRange.deleteContents(); } @@ -584,7 +584,7 @@ if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) editor.focus(); - var dropRange = getRangeAtDropPosition( editor, evt ); + dropRange = getRangeAtDropPosition( editor, evt ); if ( dropRange ) { // Paste content into the drop position. @@ -592,7 +592,7 @@ var dataTransfer = evt.data.$.dataTransfer, data; try { - data = dataTransfer.getData( 'text/html' ) + data = dataTransfer.getData( 'text/html' ); } catch ( err ) { } @@ -1340,7 +1340,7 @@ } catch ( err ) { } } - }; + } if ( sucess ) { var id = 'cke-temp-' + ( new Date() ).getTime(); From 5611cf90640f7cf2a62a5c359fd0674a36d30474 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 8 Apr 2014 11:01:59 +0200 Subject: [PATCH 11/67] Support cross editor D&D. --- plugins/clipboard/plugin.js | 154 +++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 63 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index e3f711f9736..127fcaf3ac5 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -500,87 +500,115 @@ // #11123 Firefox needs to listen on document, because otherwise event won't be fired. // #11086 IE8 cannot listen on document. - var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, - clipboard, dragRanges, dragTimestamp; + var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; + + if ( !CKEDITOR.plugins.clipboard || !CKEDITOR.plugins.clipboard.dnd ) + CKEDITOR.plugins.clipboard = { dnd: {} }; editable.attachListener( dropTarget, 'dragstart', function( evt ) { - dragTimestamp = 'cke-' + ( new Date() ).getTime(); - clipboard = editor.getSelection().getSelectedHtml(); - dragRanges = editor.getSelection().getRanges(); - evt.data.$.dataTransfer.setData( 'text', dragTimestamp ); + var clipboard = CKEDITOR.plugins.clipboard.dnd; + + clipboard.dragTimestamp = 'cke-' + ( new Date() ).getTime(); + clipboard.html = editor.getSelection().getSelectedHtml(); + clipboard.dragRanges = editor.getSelection().getRanges(); + clipboard.editor = editor; + + evt.data.$.dataTransfer.setData( 'text', clipboard.dragTimestamp ); } ); editable.attachListener( dropTarget, 'drop', function( evt ) { - var dragRange, - dropRange, - dropBookmark, + var clipboard = CKEDITOR.plugins.clipboard.dnd, + dragRanges = clipboard.dragRanges, dragBookmarks = [], - i; - - if ( evt.data.$.dataTransfer.getData( 'text' ) == dragTimestamp ) { - dropRange = getRangeAtDropPosition( editor, evt ); + dragRange, dropRange, dropBookmark, i; + + if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { + if ( clipboard.editor === editor ) { + // Internal + dropRange = getRangeAtDropPosition( editor, evt ); + + if ( dropRange ) { + // Execute D&D with a timeout because otherwise selection after + // drop is in the drag position instead of drop position. + setTimeout( function() { + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + // Fix IE 8 & 9 splitted node on drop + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && + dropRange.startContainer.type == 1 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), + offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); + } + + dropRange.startContainer = nodeBefore; + dropRange.startOffset = offset; + } - if ( dropRange ) { - // Execute D&D with a timeout because otherwise selection after - // drop is in the drag position instead of drop position. - setTimeout( function() { - editor.fire( 'saveSnapshot' ); - editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - - // Fix IE 8 & 9 splitted node on drop - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && - dropRange.startContainer.type == 1 && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { - var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), - offset = nodeBefore.getLength(); - - if ( nodeAfter ) { - nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); - nodeAfter.remove(); + // Create bookmarks in the correct order. + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); } - dropRange.startContainer = nodeBefore; - dropRange.startOffset = offset; - } + var dropRangeCopy = dropRange.clone(); + dropBookmark = dropRangeCopy.createBookmark( 1 ); - // Create bookmarks in the correct order. - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + } - var dropRangeCopy = dropRange.clone(); - dropBookmark = dropRangeCopy.createBookmark( 1 ); + // Delete content for the drag position. + for ( i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange(); + dragRange.moveToBookmark( dragBookmarks[ i ] ); + dragRange.deleteContents(); + } - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + // Paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteEvents( 'html', clipboard.html ); - // Delete content for the drag position. - for ( i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange(); - dragRange.moveToBookmark( dragBookmarks[ i ] ); - dragRange.deleteContents(); - } + editor.fire( 'unlockSnapshot' ); + }, 0 ); + } + } else { + // Cross editor + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) + editor.focus(); - // Paste content into the drop position. - dropRange = editor.createRange(); - dropRange.moveToBookmark( dropBookmark ); + dropRange = getRangeAtDropPosition( editor, evt ); + + if ( dropRange ) { + // Paste event should be before delete contents because otherwise + // Chrome have some problems with drop range: Chrome split the drop + // range container so the offset is bigger then container length. dropRange.select(); - firePasteEvents( 'html', clipboard ); + firePasteEvents( 'html', clipboard.html ); - editor.fire( 'unlockSnapshot' ); - }, 0 ); + clipboard.editor.fire( 'saveSnapshot' ); + for ( i = 0; i < clipboard.dragRanges.length; i++ ) { + clipboard.dragRanges[ i ].deleteContents(); + } + clipboard.editor.getSelection().reset(); + clipboard.editor.fire( 'saveSnapshot' ); + } } - } - // Drop from external source. - else { + } else { + // Drop from external source. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) editor.focus(); From 14d67c731f616b83ded7afc69585f074fd4a8b0f Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 8 Apr 2014 16:15:24 +0200 Subject: [PATCH 12/67] Docs and small clean-up. --- plugins/clipboard/plugin.js | 324 +++++++++++++++++++++++++----------- 1 file changed, 223 insertions(+), 101 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 127fcaf3ac5..e6ce56359a9 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -9,7 +9,7 @@ */ // -// EXECUTION FLOWS: +// COPY & PASTE EXECUTION FLOWS: // -- CTRL+C // * browser's default behaviour // -- CTRL+V @@ -77,6 +77,33 @@ // * content type sniffing (priority 6) // * markup transformations for text (priority 6) // +// DRAG & DROP EXECUTION FLOWS: +// -- Drag +// * save to the global object: +// * drag timestamp (with 'cke-' prefix), +// * selected html, +// * drag range, +// * editor instance. +// * put drag timestamp into event.dataTransfer.text +// -- Drop +// * if events text == saved timestamp && editor == saved editor +// internal drag & drop occurred +// * getRangeAtDropPosition +// * create bookmarks for drag and drop ranges starting from the end of the document +// * dragRange.deleteContents() +// * fire 'paste' with saved html +// * if events text == saved timestamp && editor != saved editor +// cross editor drag & drop occurred +// * getRangeAtDropPosition +// * fire 'paste' with saved html +// * dragRange.deleteContents() +// * FF: refreshCursor on afterPaste +// * if events text != saved timestamp +// drop form external source occurred +// * getRangeAtDropPosition +// * if event contains html data then fire 'paste' with html +// * else if event contains text data then fire 'paste' with encoded text +// * FF: refreshCursor on afterPaste 'use strict'; @@ -498,21 +525,28 @@ editable.on( 'keyup', setToolbarStates ); + // DRAG & DROP + // #11123 Firefox needs to listen on document, because otherwise event won't be fired. // #11086 IE8 cannot listen on document. var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; + // Create global object if it does not exists to handle D&D from different editor. if ( !CKEDITOR.plugins.clipboard || !CKEDITOR.plugins.clipboard.dnd ) CKEDITOR.plugins.clipboard = { dnd: {} }; + // Listed on dragstart to mark internal and cross-editor D&D + // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { var clipboard = CKEDITOR.plugins.clipboard.dnd; + // Save to the global object. clipboard.dragTimestamp = 'cke-' + ( new Date() ).getTime(); clipboard.html = editor.getSelection().getSelectedHtml(); clipboard.dragRanges = editor.getSelection().getRanges(); clipboard.editor = editor; + // dataTransfer object will be passed to the drop event. evt.data.$.dataTransfer.setData( 'text', clipboard.dragTimestamp ); } ); @@ -520,119 +554,146 @@ var clipboard = CKEDITOR.plugins.clipboard.dnd, dragRanges = clipboard.dragRanges, dragBookmarks = [], - dragRange, dropRange, dropBookmark, i; - - if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { - if ( clipboard.editor === editor ) { - // Internal + dragRange, dropBookmark, i, + // Getting drop position is one of the most complex part of D&D. dropRange = getRangeAtDropPosition( editor, evt ); - if ( dropRange ) { - // Execute D&D with a timeout because otherwise selection after - // drop is in the drag position instead of drop position. - setTimeout( function() { - editor.fire( 'saveSnapshot' ); - editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - - // Fix IE 8 & 9 splitted node on drop - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && - dropRange.startContainer.type == 1 && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { - var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), - offset = nodeBefore.getLength(); - - if ( nodeAfter ) { - nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); - nodeAfter.remove(); - } - - dropRange.startContainer = nodeBefore; - dropRange.startOffset = offset; - } - - // Create bookmarks in the correct order. - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } - - var dropRangeCopy = dropRange.clone(); - dropBookmark = dropRangeCopy.createBookmark( 1 ); - - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } - - // Delete content for the drag position. - for ( i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange(); - dragRange.moveToBookmark( dragBookmarks[ i ] ); - dragRange.deleteContents(); - } - - // Paste content into the drop position. - dropRange = editor.createRange(); - dropRange.moveToBookmark( dropBookmark ); - dropRange.select(); - firePasteEvents( 'html', clipboard.html ); - - editor.fire( 'unlockSnapshot' ); - }, 0 ); - } - } else { - // Cross editor - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) - editor.focus(); + // Cancel native drop. + evt.data.preventDefault(); - dropRange = getRangeAtDropPosition( editor, evt ); + // Do nothing if it was not possible to get drop range. + if ( !dropRange ) + return; - if ( dropRange ) { - // Paste event should be before delete contents because otherwise - // Chrome have some problems with drop range: Chrome split the drop - // range container so the offset is bigger then container length. - dropRange.select(); - firePasteEvents( 'html', clipboard.html ); + if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp && + clipboard.editor == editor ) { + // Internal D&D. - clipboard.editor.fire( 'saveSnapshot' ); - for ( i = 0; i < clipboard.dragRanges.length; i++ ) { - clipboard.dragRanges[ i ].deleteContents(); + // Execute D&D with a timeout because otherwise selection, after drop, + // on IE is in the drag position, instead of drop position. + setTimeout( function() { + // Save and lock snapshot so there will be only + // one snapshot for both remove and insert content. + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + // IE 8 & 9 split text node on drop so the first node contains + // text before drop position and the second contains rest. If we + // drag the content from the same node we will be not able to get + // it (range became invalid), so we need to join them back. + // + // Notify that first node on IE 8 & 9 is the original node object + // but with shortened content. + // + // Before: + // --- Text Node A ---------------------------------- + // /\ + // Drag position + // + // After (IE 8 & 9): + // --- Text Node A ----- --- Text Node B ----------- + // /\ /\ + // Drop position Drag position + // (invalid) + // + // After (other browsers): + // --- Text Node A ---------------------------------- + // /\ /\ + // Drop position Drag position + // + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && + dropRange.startContainer.type == 1 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), + offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); } - clipboard.editor.getSelection().reset(); - clipboard.editor.fire( 'saveSnapshot' ); + + dropRange.startContainer = nodeBefore; + dropRange.startOffset = offset; + } + + // Because we manipulate multiple ranges we need to do it carefully, + // changing one range (event creating a bookmark) may make other invalid. + // We need to change ranges into bookmark so we can manipulate them easily in the future. + // We can change the range which is later in the text before we change the preceding range. + // We call rangeBefore to test the order of ranges. + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + } + + var dropRangeCopy = dropRange.clone(); + dropBookmark = dropRangeCopy.createBookmark( 1 ); + + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + } + + // No we can safely delete content for the drag ranges... + for ( i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange(); + dragRange.moveToBookmark( dragBookmarks[ i ] ); + dragRange.deleteContents(); } - } + // ...and paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteEvents( 'html', clipboard.html ); + + editor.fire( 'unlockSnapshot' ); + }, 0 ); + } else if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { + // Cross editor D&D. + + // Paste event should be fired before delete contents because otherwise + // Chrome have a problem with drop range (Chrome split the drop + // range container so the offset is bigger then container length). + dropRange.select(); + firePasteEvents( 'html', clipboard.html ); + + // Remove dragged content and make a snapshot. + clipboard.editor.fire( 'saveSnapshot' ); + for ( i = 0; i < clipboard.dragRanges.length; i++ ) { + clipboard.dragRanges[ i ].deleteContents(); + } + clipboard.editor.getSelection().reset(); + clipboard.editor.fire( 'saveSnapshot' ); } else { // Drop from external source. - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) - editor.focus(); - dropRange = getRangeAtDropPosition( editor, evt ); + // Paste content into the drop position. + dropRange.select(); + var dataTransfer = evt.data.$.dataTransfer, + data; - if ( dropRange ) { - // Paste content into the drop position. - dropRange.select(); - var dataTransfer = evt.data.$.dataTransfer, - data; - try { - data = dataTransfer.getData( 'text/html' ); - } catch ( err ) { - } + // IE support only text data and throws exception if we try to get html data. + // This html data object may also be empty if we drag content of the textarea. + try { + data = dataTransfer.getData( 'text/html' ); + } catch ( err ) { + } + if ( data ) + // If we have html data we fire paste event with it. + firePasteEvents( 'html', data ); + else { + // Try to get text data otherwise. + data = dataTransfer.getData( 'Text' ); if ( data ) - firePasteEvents( 'html', data ); - else { - data = dataTransfer.getData( 'Text' ); firePasteEvents( 'text', CKEDITOR.tools.htmlEncode( data ) ); - } } } - evt.data.preventDefault(); } ); } @@ -1349,11 +1410,27 @@ return defaultRange; // IE 8. else if ( document.body.createTextRange ) { + // If we drop content from the different source we need to call focus on IE8. + editor.focus(); + $range = editor.document.getBody().$.createTextRange(); try { var sucess = false; - for ( var i = 0; i < 20 && !sucess; i++ ) { + // If user drop between text line IEs moveToPoint throws exception: + // + // Lorem ipsum pulvinar purus et euismod + // + // dolor sit amet,| consectetur adipiscing + // * + // vestibulum tincidunt augue eget tempus. + // + // * - drop position + // | - expected cursor position + // + // So we try to call moveToPoint with +-1px up to +-20px above or + // below original drop position to find nearest good drop position. + for ( var i = 0; i < 20 && !sucess; i++ ) { if ( !sucess ) { try { $range.moveToPoint( x, y - i ); @@ -1378,7 +1455,23 @@ range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); span.remove(); } else { - // Try to use elementFromPoint. + // If the fist method does not succeed we might be next to + // the short element (like header): + // + // Lorem ipsum pulvinar purus et euismod. + // + // + // SOME HEADER| * + // + // + // vestibulum tincidunt augue eget tempus. + // + // * - drop position + // | - expected cursor position + // + // In such situation elementFromPoint returns proper element. Using getClientRect + // it is possible to check if the cursor should be at the beginning or at the end + // of paragraph. var $element = editor.document.$.elementFromPoint( x, y ), element = new CKEDITOR.dom.element( $element ), rect; @@ -1394,11 +1487,24 @@ range.collapse( true ); } } - // elementFromPoint if editable try with defaultRange. + // If drop happens on no element elementFromPoint returns html or body. + // + // * |Lorem ipsum pulvinar purus et euismod. + // + // vestibulum tincidunt augue eget tempus. + // + // * - drop position + // | - expected cursor position + // + // In such case we can try to use default selection. If startContainer is not + // 'editable' element it is probably proper selection. else if ( defaultRange && defaultRange.startContainer && !defaultRange. startContainer.equals( editor.editable() ) ) { return defaultRange; - } else + } + // Otherwise we can not find any drop position and we have to return null + // and cancel D&D event. + else return null; } @@ -1412,11 +1518,27 @@ return range; } + // Check if the beginning of the firstRange is before the beginning of the secondRange + // and modification of the content in the firstRange may break secondRange. + // + // Notify that this function returns false if these two ranges are in two + // separate nodes and do not affect each other (even if firstRange is before secondRange). function rangeBefore( firstRange, secondRange ) { + // Both ranges has the same parent and the first has smaller offset. E.g.: + // + // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." + // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." + // if ( firstRange.startContainer.equals( secondRange.startContainer ) && firstRange.startOffset < secondRange.startOffset ) return true; + // First range is inside a text node and the second is not, but if we change the + // first range into bookmark and split the text node then the seconds node offset + // will be no longer correct. + // + // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." + // if ( firstRange.startContainer.getParent().equals( secondRange.startContainer ) && firstRange.startContainer.getIndex() < secondRange.startOffset ) return true; From 666946e3e95bfd61a92f39ea9fb043ae61f4a6f6 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 8 Apr 2014 16:43:42 +0200 Subject: [PATCH 13/67] Check if evt.data.$.dataTransfer.getData( 'text' ) is not empty. --- plugins/clipboard/plugin.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index e6ce56359a9..1f31f3b3995 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -565,7 +565,8 @@ if ( !dropRange ) return; - if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp && + if ( evt.data.$.dataTransfer.getData( 'text' ) && + evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp && clipboard.editor == editor ) { // Internal D&D. @@ -653,7 +654,8 @@ editor.fire( 'unlockSnapshot' ); }, 0 ); - } else if ( evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { + } else if ( evt.data.$.dataTransfer.getData( 'text' ) && + evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { // Cross editor D&D. // Paste event should be fired before delete contents because otherwise From f0ac6cd2ab3029fbc3e8900e59a74c608016802d Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 9 Jun 2014 15:52:37 +0200 Subject: [PATCH 14/67] Port drag&drop tests to Bender. --- tests/plugins/clipboard/drop.html | 14 ++ tests/plugins/clipboard/drop.js | 366 ++++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 tests/plugins/clipboard/drop.html create mode 100644 tests/plugins/clipboard/drop.js diff --git a/tests/plugins/clipboard/drop.html b/tests/plugins/clipboard/drop.html new file mode 100644 index 00000000000..1d08d227bf7 --- /dev/null +++ b/tests/plugins/clipboard/drop.html @@ -0,0 +1,14 @@ + +
+
+ +
+
+ +
+
+
+
+ +
+ \ No newline at end of file diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js new file mode 100644 index 00000000000..119570068c8 --- /dev/null +++ b/tests/plugins/clipboard/drop.js @@ -0,0 +1,366 @@ +/* bender-tags: editor,unit */ +/* bender-ckeditor-plugins: toolbar,clipboard,undo */ + +'use strict'; + +CKEDITOR.disableAutoInline = true; + +function createDnDEventMock() { + return { + $: { + clientX: 0, + clientY: 0, + + dataTransfer: { + setData: function( type, data ) { + this.type = data; + }, + getData: function( type ) { + return this.type; + }, + } + }, + preventDefault: function() { + // noop + } + } + } + +function drag( editor, evt ) { + var editable = editor.editable(), + dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; + + dropTarget.fire( 'dragstart', evt ); +} + +function drop( editor, evt, config, callback ) { + // document.caretRangeFromPoint returns null + // for inline and divbased editor if viewport is too small. + if ( CKEDITOR.env.webkit && editor.name != 'framed' && window.innerHeight < 300 ) + assert.ignore(); + + // IE8 also has some problem with running test if the window is too small. + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && editor.name != 'framed' && document.documentElement.clientHeight < 500 ) + assert.ignore(); + + var editable = editor.editable(), + dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, + range = new CKEDITOR.dom.range( editor.document ), + pasteEventCounter = 0, + expectedPasteEventCount = typeof config.expectedPasteEventCount !== 'undefined' ? config.expectedPasteEventCount : 1, + x = config.x[ editor.name ] ? config.x[ editor.name ] : config.x, + y = config.y[ editor.name ] ? config.y[ editor.name ] : config.y; + + // To simulate drop event we need to set selection (IE use it), + // clientX and clientY (IE and Chrome use it), + // rangeParent and rangeOffset (FF use it). + range.setStart( config.element, config.offset ); + range.collapse( true ); + range.select(); + + editor.focus(); + + evt.$.clientX = x; + evt.$.clientY = y; + if ( CKEDITOR.env.gecko ) { + evt.$.rangeParent = config.element.$; + evt.$.rangeOffset = config.offset; + } + + editor.on( 'paste', function() { + pasteEventCounter++; + } ); + + dropTarget.fire( 'drop', evt ); + + wait( function() { + assert.areSame( expectedPasteEventCount, pasteEventCounter, 'paste event should be called ' + expectedPasteEventCount + ' time(s)' ); + + callback(); + }, 100 ); +} + +var logViewTopBackup, + editorsDefinitions = { + framed: { + name: 'framed', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + inline: { + name: 'inline', + creator: 'inline', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + divarea: { + name: 'divarea', + creator: 'replace', + config: { + extraPlugins: 'divarea', + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + cross: { + name: 'cross', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + } + }, + contentsFrame = CKEDITOR.env.webkit && CKEDITOR.document && CKEDITOR.document.getWindow().$.frameElement; + + contentsFrame && ( contentsFrame.style.width = '1%' ); + contentsFrame && ( contentsFrame.style.width = '1000px' ); + +bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { + bender.test( bender.tools.createTestsForEditors( + [ editors.framed, editors.inline, editors.divarea ], { + setUp: function() { + CKEDITOR.document.getById( 'framed-container' ).hide(); + CKEDITOR.document.getById( 'divarea-container' ).hide(); + CKEDITOR.document.getById( 'inline-container' ).hide(); + + logViewTopBackup = CKEDITOR.document.findOne( '.results' ).getStyle( 'top' ); + CKEDITOR.document.findOne( '.results' ).setStyle( 'top', 'auto' ); + }, + + tearDown: function() { + + CKEDITOR.document.findOne( '.results' ).setStyle( 'top', logViewTopBackup ); + }, + + 'test drop to header': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Header1

' + + '

Lorem ipsum [dolor] sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + x: 260, + y: { framed: 33, inline: 111, divarea: 172 }, + element: editor.document.getById( 'h1' ).getChild( 0 ), + offset: 7 + }, function() { + assert.areSame( '

Header1dolor^

Lorem ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Header1

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop the same line, before': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem ipsum [dolor] sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + x: { framed: 60, inline: 45, divarea: 54 }, + y: { framed: 131, inline: 112, divarea: 216 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 6 + }, function() { + assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop the same line, after': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + x: { framed: 151, inline: ( CKEDITOR.env.webkit ? 142 : 139 ), divarea: 151 }, + y: { framed: 38, inline: 112, divarea: 224 }, + element: editor.document.getById( 'p' ).getChild( 2 ), + offset: 11 + }, function() { + assert.areSame( '

Lorem dolor sit ipsum^amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop after paragraph': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + x: { framed: 251, inline: 240, divarea: 251 }, + y: { framed: 38, inline: 112, divarea: 224 }, + element: editor.document.getById( 'p' ).getChild( 2 ), + offset: 16 + }, function() { + assert.areSame( '

Lorem dolor sit amet.ipsum^

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop on the left from paragraph': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + x: 15, + y: { framed: 38, inline: 112, divarea: 224 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 0 + }, function() { + assert.isMatching( /

ipsum\^Lorem dolor sit amet\.<\/p>/, bender.tools.compatHtml( bender.tools.getHtmlWithSelection( editor ), 0, 1, 0, 1 ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.isMatching( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 1, 1, 1, 1 ), 'after undo' ); + } ); + }, + + 'test drop from external source': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + + if ( CKEDITOR.env.ie ) + evt.$.dataTransfer.setData( 'Text', 'dolor' ); + else + evt.$.dataTransfer.setData( 'text/html', 'dolor' ); + + drop( editor, evt, { + x: { framed: 60, inline: 45, divarea: 54 }, + y: { framed: 131, inline: 112, divarea: 216 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 6 + }, function() { + assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop html from external source': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + + if ( CKEDITOR.env.ie ) + evt.$.dataTransfer.setData( 'Text', 'dolor' ); + else + evt.$.dataTransfer.setData( 'text/html', 'dolor' ); + + drop( editor, evt, { + x: { framed: 60, inline: 45, divarea: 54 }, + y: { framed: 131, inline: 112, divarea: 216 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 6 + }, function() { + assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + } ); + }, + + 'test drop empty element from external source': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(); + + bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); + + drop( editor, evt, { + x: { framed: 60, inline: 45, divarea: 54 }, + y: { framed: 131, inline: 112, divarea: 216 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 6, + expectedPasteEventCount: 0 + }, function() { + assert.areSame( '

Lorem ^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + } ); + }, + + 'test cross editor drop': function( editor ) { + CKEDITOR.document.getById( editor.name + '-container' ).show(); + + var bot = editorBots[ editor.name ], + evt = createDnDEventMock(), + botCross = editorBots[ 'cross' ], + editorCross = botCross.editor; + + bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + botCross.setHtmlWithSelection( '

Lorem [ipsum dolor] sit amet.

' ); + + drag( editorCross, evt ); + + drop( editor, evt, { + x: { framed: 60, inline: 45, divarea: 54 }, + y: { framed: 131, inline: 112, divarea: 216 }, + element: editor.document.getById( 'p' ).getChild( 0 ), + offset: 6 + }, function() { + assert.areSame( '

Lorem ipsum dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + + assert.areSame( '

Lorem sit amet.

', bender.tools.compatHtml( editorCross.getData(), 0, 1, 0, 1 ), 'after drop - editor cross' ); + + editor.execCommand( 'undo' ); + editorCross.execCommand( 'undo' ); + + assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editorCross.getData(), 0, 1, 0, 1 ), 'after undo - editor cross' ); + } ); + } + } ) ); +} ); \ No newline at end of file From 0c30e9a730f84b5c7b6a3d1b68db39b08d3d9376 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 9 Jun 2014 15:59:49 +0200 Subject: [PATCH 15/67] Move DnD manual tests to plugins/clipboard/dev/. --- plugins/clipboard/dev/dnd.html | 173 +++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 plugins/clipboard/dev/dnd.html diff --git a/plugins/clipboard/dev/dnd.html b/plugins/clipboard/dev/dnd.html new file mode 100644 index 00000000000..331a30e5690 --- /dev/null +++ b/plugins/clipboard/dev/dnd.html @@ -0,0 +1,173 @@ + + + + + + Manual test for #11460 + + + + + + + +

+ Manual test for #11460 +

+

Description (hide/show)

+
+

Test internal D&D in the editor, dropping content from an external source (helpers, MS Word) and D&D between editors. Keep in mind that internal D&D is the most complex operation because editor have to handle two ranges at the same time.

+

Expected behavior:

+
    +
  • proper drop position,
  • +
  • in the internal and cross editor D&D: dragged content should be removed,
  • +
  • dropped content should be (more less) the same as dragged content,
  • +
  • paste event should be fired,
  • +
  • undo should work properly (one undo operation for one D&D),
  • +
  • no crashes, nor errors,
  • +
+

Drag scenarios:

+
    +
  • drag simple text,
  • +
  • drag table cell/cells,
  • +
  • drag link,
  • +
  • drag helpers textarea content,
  • +
  • drag helpers html content,
  • +
  • drag content from MS Word.
  • +
+

Drop scenarios:

+
    +
  • drop in the different paragraph (before and after),
  • +
  • drop in the same paragraph (before and after),
  • +
  • drop in the same text node (before and after),
  • +
  • drop between text lines,
  • +
  • drop on the whitespace next to the header,
  • +
  • drop on the whitespace on the left side from the quote,
  • +
  • drop into a cell.
  • +
+

Known issues (not part of this ticket):

+
    +
  • because of #11636 dragged content is not correct in some cases (e.g. when you drag part of the link),
  • +
  • drag position needs clean up after D&D (e.g. remove empty paragraphs, fix table),
  • +
  • drop position needs clean up after D&D (e.g. add spaces before/after dropped content, apply parents styles, break paragraph when one paragraph is dropped at the end to the other paragraph),
  • +
  • in the external D&D: Chrome add plenty of addition tags.
  • +
+
+
+

Helpers (hide/show)

+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. In commodo vulputate tempor. Sed <b>at elit</b> vel ligula mollis aliquet a ac odio. +
+Aenean cursus egestas ipsum.
+				
+
+
+
+
+
+

Classic editor (hide/show)

+
+ +
+
+
+

Inline editor (hide/show)

+
+

Saturn V carrying Apollo 11 Apollo 11

+ +

Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

+ +

Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.

+ +

Broadcasting and quotes

+ +

Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

+ +
+

One small step for [a] man, one giant leap for mankind.

+
+ +

Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

+ +
+

[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

+
+ +

Technical details

+ + + + + + + + + + + + + + + + + + + + + + + +
Mission crew
PositionAstronaut
CommanderNeil A. Armstrong
Command Module PilotMichael Collins
Lunar Module PilotEdwin "Buzz" E. Aldrin, Jr.
+ +

Launched by a Saturn V rocket from Kennedy Space Center in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of NASA's Apollo program. The Apollo spacecraft had three parts:

+ +
    +
  1. Command Module with a cabin for the three astronauts which was the only part which landed back on Earth
  2. +
  3. Service Module which supported the Command Module with propulsion, electrical power, oxygen and water
  4. +
  5. Lunar Module for landing on the Moon.
  6. +
+ +

After being sent to the Moon by the Saturn V's upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the Sea of Tranquility. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the Pacific Ocean on July 24.

+ +
+

Source: Wikipedia.org

+
+
+ + + From 78be1fc9d6624ce0e635188a045292b2bb214063 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 12 Jun 2014 18:09:19 +0200 Subject: [PATCH 16/67] Fixed issue with the timestamp in the textarea. Core refactoring. Created dataTransfer facade. --- plugins/clipboard/plugin.js | 308 ++++++++++++++++++++++++++++++------ 1 file changed, 259 insertions(+), 49 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 1f31f3b3995..22b9a565467 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -531,32 +531,43 @@ // #11086 IE8 cannot listen on document. var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; - // Create global object if it does not exists to handle D&D from different editor. - if ( !CKEDITOR.plugins.clipboard || !CKEDITOR.plugins.clipboard.dnd ) - CKEDITOR.plugins.clipboard = { dnd: {} }; - // Listed on dragstart to mark internal and cross-editor D&D // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { - var clipboard = CKEDITOR.plugins.clipboard.dnd; - - // Save to the global object. - clipboard.dragTimestamp = 'cke-' + ( new Date() ).getTime(); - clipboard.html = editor.getSelection().getSelectedHtml(); - clipboard.dragRanges = editor.getSelection().getRanges(); - clipboard.editor = editor; + // Create a dataTransfer object and save it to the global clipboard.dnd. + CKEDITOR.plugins.clipboard.dnd = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); + } ); - // dataTransfer object will be passed to the drop event. - evt.data.$.dataTransfer.setData( 'text', clipboard.dragTimestamp ); + // Clean up on dragend. + editable.attachListener( dropTarget, 'dragend', function( evt ) { + // When DnD is done we need to remove clipboard.dnd so the future + // external drop will be not recognize as internal. + CKEDITOR.plugins.clipboard.dnd = undefined; } ); editable.attachListener( dropTarget, 'drop', function( evt ) { - var clipboard = CKEDITOR.plugins.clipboard.dnd, - dragRanges = clipboard.dragRanges, - dragBookmarks = [], - dragRange, dropBookmark, i, - // Getting drop position is one of the most complex part of D&D. - dropRange = getRangeAtDropPosition( editor, evt ); + // Create a new dataTransfer object based on the drop event. + // If this event was used on dragstart to create dataTransfer + // both dataTransfer objects will have the same id. + var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); + + // If there is the same id we will replace dataTransfer with the one + // created on drag, because it contains drag editor, drag content and so on. + // Otherwise (in case of drag from external source) we save new object to + // the global clipboard.dnd. + if ( CKEDITOR.plugins.clipboard.dnd && + dataTransfer.id == CKEDITOR.plugins.clipboard.dnd.id ) { + dataTransfer = CKEDITOR.plugins.clipboard.dnd; + } else { + CKEDITOR.plugins.clipboard.dnd = dataTransfer; + } + + dataTransfer.setTargetEditor( editor ); + + + // Getting drop position is one of the most complex part of D&D. + var dropRange = getRangeAtDropPosition( editor, evt ), + i; // Cancel native drop. evt.data.preventDefault(); @@ -565,14 +576,16 @@ if ( !dropRange ) return; - if ( evt.data.$.dataTransfer.getData( 'text' ) && - evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp && - clipboard.editor == editor ) { + if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { // Internal D&D. // Execute D&D with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { + var dragRanges = dataTransfer.sourceRanges, + dragBookmarks = [], + dragRange, dropBookmark; + // Save and lock snapshot so there will be only // one snapshot for both remove and insert content. editor.fire( 'saveSnapshot' ); @@ -650,51 +663,33 @@ dropRange = editor.createRange(); dropRange.moveToBookmark( dropBookmark ); dropRange.select(); - firePasteEvents( 'html', clipboard.html ); + firePasteEvents( 'html', dataTransfer.dataValue ); editor.fire( 'unlockSnapshot' ); }, 0 ); - } else if ( evt.data.$.dataTransfer.getData( 'text' ) && - evt.data.$.dataTransfer.getData( 'text' ) == clipboard.dragTimestamp ) { + } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { // Cross editor D&D. // Paste event should be fired before delete contents because otherwise // Chrome have a problem with drop range (Chrome split the drop // range container so the offset is bigger then container length). dropRange.select(); - firePasteEvents( 'html', clipboard.html ); + firePasteEvents( 'html', dataTransfer.dataValue ); // Remove dragged content and make a snapshot. - clipboard.editor.fire( 'saveSnapshot' ); - for ( i = 0; i < clipboard.dragRanges.length; i++ ) { - clipboard.dragRanges[ i ].deleteContents(); + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); + for ( i = 0; i < dataTransfer.sourceRanges.length; i++ ) { + dataTransfer.sourceRanges[ i ].deleteContents(); } - clipboard.editor.getSelection().reset(); - clipboard.editor.fire( 'saveSnapshot' ); + dataTransfer.sourceEditor.getSelection().reset(); + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); } else { // Drop from external source. // Paste content into the drop position. dropRange.select(); - var dataTransfer = evt.data.$.dataTransfer, - data; - - // IE support only text data and throws exception if we try to get html data. - // This html data object may also be empty if we drag content of the textarea. - try { - data = dataTransfer.getData( 'text/html' ); - } catch ( err ) { - } - if ( data ) - // If we have html data we fire paste event with it. - firePasteEvents( 'html', data ); - else { - // Try to get text data otherwise. - data = dataTransfer.getData( 'Text' ); - if ( data ) - firePasteEvents( 'text', CKEDITOR.tools.htmlEncode( data ) ); - } + firePasteEvents( dataTransfer.dataType, dataTransfer.dataValue ); } } ); @@ -1547,6 +1542,221 @@ return false; } + + // Data type used to link drag and drop events. + var clipboardIdDataType = + // IE does not support different data types that Text and URL. + // In IE 9- we can use URL data type to mark that drag comes from the editor. + ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) ? 'URL': + // In IE 10+ URL data type is buggie and there is no way to mark DnD without + // modifying text data (which would be displayed if user drop content to the textarea) + // so we just read dragged text. + CKEDITOR.env.ie ? 'Text' : + // In Chrome and Firefox we can use custom data types. + 'cke/id'; + + /** + * @private + * @singleton + * @class CKEDITOR.plugins.clipboard + */ + CKEDITOR.plugins.clipboard = {}; + + /** + * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences + * between browsers. + * + * @class CKEDITOR.plugins.clipboard.dataTransfer + * @constructor Creates a class instance and . + * + * @param {Object} domEvent + * A native DOM event object. + * + * @param {CKEDITOR.editor} editor The source editor instance. + * If this is set then html and sourceRanges will be created based on the editor contents. + */ + CKEDITOR.plugins.clipboard.dataTransfer = function( evt, editor ) { + this.$ = evt.data.$.dataTransfer; + + // Check if ID is already created. + this.id = this.$.getData( clipboardIdDataType ); + + // If there is no ID we need to create it. Different browsers needs different ID. + if ( !this.id ) { + if ( clipboardIdDataType == 'URL' ) { + // For IEs URL type ID have to look like an URL. + this.id = 'http://cke.' + ( new Date() ).getTime() +'/'; + } else if ( clipboardIdDataType == 'Text' ) { + // For IE10+ only Text data type is supported and we have to compare dragged + // and dropped text. If the ID is not set it means that empty string was dragged + // (ex. image with no alt). We change null to empty string. + this.id = ""; + } else { + // String for custom data type. + this.id = 'cke-' + ( new Date() ).getTime(); + } + } + + // In IE10+ we can not use any data type besides text, so we do not call setData. + if ( evt.name != 'drop' && clipboardIdDataType != 'Text' ) { + // dataTransfer object will be passed from the drag to the drop event. + this.$.setData( clipboardIdDataType, this.id ); + } + + if ( editor ) { + this.sourceEditor = editor; + this.dataValue = editor.getSelection().getSelectedHtml(); + this.dataType = 'html'; + this.sourceRanges = editor.getSelection().getRanges(); + } else { + // IE support only text data and throws exception if we try to get html data. + // This html data object may also be empty if we drag content of the textarea. + try { + this.dataValue = this.getData( 'text/html' ); + this.dataType = 'html'; + } catch ( err ) { + } + + if ( !this.dataValue ) { + // Try to get text data otherwise. + this.dataValue = this.getData( 'Text' ); + + if ( this.dataValue ) { + CKEDITOR.tools.htmlEncode( this.getData( 'Text' ) ); + this.dataType = 'text'; + } + } + } + }; + + /** + * Facade for the native getData method. + * + * @param {String} type The type of data to retrieve. + * + * @returns {String} type + * Stored data for the given type or an + * empty string if data for that type does not exist. + */ + CKEDITOR.plugins.clipboard.dataTransfer.prototype.getData = function( type ) { + return this.$.getData( type ); + }; + + /** + * Facade for the native setData method. + * + * @param {String} type The type of data to retrieve. + * @param {String} value The data to add. + */ + CKEDITOR.plugins.clipboard.dataTransfer.prototype.setData = function( type, value ) { + return this.$.getData( type, value ); + }; + + /** + * Set target editor. + * + * @param {CKEDITOR.editor} editor The target editor instance. + */ + CKEDITOR.plugins.clipboard.dataTransfer.prototype.setTargetEditor = function( editor ) { + this.targetEditor = editor; + }; + + /** + * Data transfer operation (drag and drop or copy and pasted) started and ended in the same + * editor instance. + * + * @readonly + * @property {Number} [=0] + * @member CKEDITOR + */ + CKEDITOR.DATA_TRANSFER_INTERNAL = 0; + + /** + * Data transfer operation (drag and drop or copy and pasted) started and ended in the + * instance of CKEditor but in two different editors. + * + * @readonly + * @property {Number} [=1] + * @member CKEDITOR + */ + CKEDITOR.DATA_TRANSFER_CROSS_EDITORS = 1; + + /** + * Data transfer operation (drag and drop or copy and pasted) started not in the CKEditor. + * The source of the data may be textarea, HTML, another application, etc.. + * + * @readonly + * @property {Number} [=2] + * @member CKEDITOR + */ + CKEDITOR.DATA_TRANSFER_EXTERNAL = 2; + + /** + * Get data transfer type. + * + * @returns {Number} type + * Possible options: DATA_TRANSFER_INTERNAL, DATA_TRANSFER_CROSS_EDITORS, DATA_TRANSFER_EXTERNAL. + */ + CKEDITOR.plugins.clipboard.dataTransfer.prototype.getTransferType = function() { + if ( !this.sourceEditor ) { + return CKEDITOR.DATA_TRANSFER_EXTERNAL; + } else if ( this.sourceEditor == this.targetEditor ) { + return CKEDITOR.DATA_TRANSFER_INTERNAL; + } else { + return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS; + } + }; + + /** + * ID + * + * @property {String} id + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * $ + * + * @private + * @property {Object} $ + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * sourceEditor + * + * @property {CKEDITOR.editor} sourceEditor + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * targetEditor + * + * @property {CKEDITOR.editor} targetEditor + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * dataValue + * + * @property {String} dataValue + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * dataType + * + * @property {String} dataType + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ + + /** + * Range instances that represent the selection during drag. + * + * @private + * @property {Array} sourceRanges + * @member CKEDITOR.plugins.clipboard.dataTransfer + */ } )(); /** From c5c4d0f984705c5ff13cd37bb13621b6a5f957ba Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 13 Jun 2014 09:53:54 +0200 Subject: [PATCH 17/67] Fixed typo. --- plugins/clipboard/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 22b9a565467..57b78322cc4 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1649,7 +1649,7 @@ * @param {String} value The data to add. */ CKEDITOR.plugins.clipboard.dataTransfer.prototype.setData = function( type, value ) { - return this.$.getData( type, value ); + return this.$.setData( type, value ); }; /** From 66dcfba8b3033aaec27931b74fe2319190f1225f Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 23 Jun 2014 16:04:01 +0200 Subject: [PATCH 18/67] Fix for Safari. --- plugins/clipboard/plugin.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 57b78322cc4..00e4532925e 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -536,6 +536,11 @@ editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it to the global clipboard.dnd. CKEDITOR.plugins.clipboard.dnd = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); + + // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. + if ( CKEDITOR.env.safari ) { + evt.data.$.dataTransfer.setData( 'text', evt.data.$.dataTransfer.getData( 'text' ) ); + } } ); // Clean up on dragend. From 0f06cd1671ddba81bd8a62e77e01075aa3897789 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 13:23:07 +0200 Subject: [PATCH 19/67] Fixed gecko disappearing cursor. --- plugins/clipboard/plugin.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 00e4532925e..511f2c13acf 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -675,6 +675,11 @@ } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { // Cross editor D&D. + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); + } + // Paste event should be fired before delete contents because otherwise // Chrome have a problem with drop range (Chrome split the drop // range container so the offset is bigger then container length). @@ -691,6 +696,11 @@ } else { // Drop from external source. + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); + } + // Paste content into the drop position. dropRange.select(); @@ -698,6 +708,12 @@ } } ); + function fixGeckoDisappearingCursor( editor ) { + editor.once( 'afterPaste', function() { + editor.toolbox.focus(); + } ); + } + } // Create object representing Cut or Copy commands. From 76d76e29aed290c91e7d4935f48ecf24fb1f4add Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 16:54:54 +0200 Subject: [PATCH 20/67] Extracted some methods to make code more readable. Made some methods public to make testing simpler. --- plugins/clipboard/plugin.js | 400 ++++++++++++++++++++---------------- 1 file changed, 227 insertions(+), 173 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 511f2c13acf..6606c3d9c28 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -535,7 +535,7 @@ // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it to the global clipboard.dnd. - CKEDITOR.plugins.clipboard.dnd = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); + CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. if ( CKEDITOR.env.safari ) { @@ -551,28 +551,11 @@ } ); editable.attachListener( dropTarget, 'drop', function( evt ) { - // Create a new dataTransfer object based on the drop event. - // If this event was used on dragstart to create dataTransfer - // both dataTransfer objects will have the same id. - var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); - - // If there is the same id we will replace dataTransfer with the one - // created on drag, because it contains drag editor, drag content and so on. - // Otherwise (in case of drag from external source) we save new object to - // the global clipboard.dnd. - if ( CKEDITOR.plugins.clipboard.dnd && - dataTransfer.id == CKEDITOR.plugins.clipboard.dnd.id ) { - dataTransfer = CKEDITOR.plugins.clipboard.dnd; - } else { - CKEDITOR.plugins.clipboard.dnd = dataTransfer; - } - - dataTransfer.setTargetEditor( editor ); - + // Create dataTransfer of get it, if it was created before. + var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, null, editor ); // Getting drop position is one of the most complex part of D&D. - var dropRange = getRangeAtDropPosition( editor, evt ), - i; + var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ); // Cancel native drop. evt.data.preventDefault(); @@ -582,138 +565,116 @@ return; if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { - // Internal D&D. + internalDnD( dropRange, dataTransfer ); + } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { + crossEditorDnD( dropRange, dataTransfer ); + } else { + externalDnD( dropRange, dataTransfer ); + } + } ); + } - // Execute D&D with a timeout because otherwise selection, after drop, - // on IE is in the drag position, instead of drop position. - setTimeout( function() { - var dragRanges = dataTransfer.sourceRanges, - dragBookmarks = [], - dragRange, dropBookmark; - - // Save and lock snapshot so there will be only - // one snapshot for both remove and insert content. - editor.fire( 'saveSnapshot' ); - editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - - // IE 8 & 9 split text node on drop so the first node contains - // text before drop position and the second contains rest. If we - // drag the content from the same node we will be not able to get - // it (range became invalid), so we need to join them back. - // - // Notify that first node on IE 8 & 9 is the original node object - // but with shortened content. - // - // Before: - // --- Text Node A ---------------------------------- - // /\ - // Drag position - // - // After (IE 8 & 9): - // --- Text Node A ----- --- Text Node B ----------- - // /\ /\ - // Drop position Drag position - // (invalid) - // - // After (other browsers): - // --- Text Node A ---------------------------------- - // /\ /\ - // Drop position Drag position - // - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 && - dropRange.startContainer.type == 1 && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRanges[ 0 ].startContainer ) ) { - var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), - offset = nodeBefore.getLength(); - - if ( nodeAfter ) { - nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); - nodeAfter.remove(); - } - - dropRange.startContainer = nodeBefore; - dropRange.startOffset = offset; - } + // Internal D&D. + function internalDnD( dropRange, dataTransfer ) { + // Execute D&D with a timeout because otherwise selection, after drop, + // on IE is in the drag position, instead of drop position. + setTimeout( function() { + var dragRanges = dataTransfer.sourceRanges, + dragBookmarks = [], + dragRange, dropBookmark, i, - // Because we manipulate multiple ranges we need to do it carefully, - // changing one range (event creating a bookmark) may make other invalid. - // We need to change ranges into bookmark so we can manipulate them easily in the future. - // We can change the range which is later in the text before we change the preceding range. - // We call rangeBefore to test the order of ranges. - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + // Functions shortcuts. + rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore, + fixIESplittedNodes = CKEDITOR.plugins.clipboard.fixIESplittedNodes; - var dropRangeCopy = dropRange.clone(); - dropBookmark = dropRangeCopy.createBookmark( 1 ); + // Save and lock snapshot so there will be only + // one snapshot for both remove and insert content. + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { + fixIESplittedNodes( dragRanges[ 0 ], dropRange ); + } - // No we can safely delete content for the drag ranges... - for ( i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange(); - dragRange.moveToBookmark( dragBookmarks[ i ] ); - dragRange.deleteContents(); - } + // Because we manipulate multiple ranges we need to do it carefully, + // changing one range (event creating a bookmark) may make other invalid. + // We need to change ranges into bookmark so we can manipulate them easily in the future. + // We can change the range which is later in the text before we change the preceding range. + // We call rangeBefore to test the order of ranges. + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + } - // ...and paste content into the drop position. - dropRange = editor.createRange(); - dropRange.moveToBookmark( dropBookmark ); - dropRange.select(); - firePasteEvents( 'html', dataTransfer.dataValue ); + var dropRangeCopy = dropRange.clone(); + dropBookmark = dropRangeCopy.createBookmark( 1 ); - editor.fire( 'unlockSnapshot' ); - }, 0 ); - } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { - // Cross editor D&D. + for ( i = 0; i < dragRanges.length; i++ ) { + dragRange = dragRanges[ i ]; + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmarks.push( dragRange.createBookmark( 1 ) ); + } - // Because of FF bug we need to use this hack, otherwise cursor is hidden. - if ( CKEDITOR.env.gecko ) { - fixGeckoDisappearingCursor( editor ); - } + // No we can safely delete content for the drag ranges... + for ( i = 0; i < dragBookmarks.length; i++ ) { + dragRange = editor.createRange(); + dragRange.moveToBookmark( dragBookmarks[ i ] ); + dragRange.deleteContents(); + } - // Paste event should be fired before delete contents because otherwise - // Chrome have a problem with drop range (Chrome split the drop - // range container so the offset is bigger then container length). - dropRange.select(); - firePasteEvents( 'html', dataTransfer.dataValue ); + // ...and paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteEvents( 'html', dataTransfer.dataValue ); - // Remove dragged content and make a snapshot. - dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - for ( i = 0; i < dataTransfer.sourceRanges.length; i++ ) { - dataTransfer.sourceRanges[ i ].deleteContents(); - } - dataTransfer.sourceEditor.getSelection().reset(); - dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - } else { - // Drop from external source. + editor.fire( 'unlockSnapshot' ); + }, 0 ); + } - // Because of FF bug we need to use this hack, otherwise cursor is hidden. - if ( CKEDITOR.env.gecko ) { - fixGeckoDisappearingCursor( editor ); - } + // Cross editor D&D. + function crossEditorDnD( dropRange, dataTransfer ) { + var i; - // Paste content into the drop position. - dropRange.select(); + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); + } - firePasteEvents( dataTransfer.dataType, dataTransfer.dataValue ); - } - } ); + // Paste event should be fired before delete contents because otherwise + // Chrome have a problem with drop range (Chrome split the drop + // range container so the offset is bigger then container length). + dropRange.select(); + firePasteEvents( 'html', dataTransfer.dataValue ); - function fixGeckoDisappearingCursor( editor ) { - editor.once( 'afterPaste', function() { - editor.toolbox.focus(); - } ); + // Remove dragged content and make a snapshot. + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); + for ( i = 0; i < dataTransfer.sourceRanges.length; i++ ) { + dataTransfer.sourceRanges[ i ].deleteContents(); + } + dataTransfer.sourceEditor.getSelection().reset(); + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); + } + + // Drop from external source. + function externalDnD( dropRange, dataTransfer ) { + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); } + // Paste content into the drop position. + dropRange.select(); + + firePasteEvents( dataTransfer.dataType, dataTransfer.dataValue ); + } + + // Fix for Gecko bug with disappearing cursor. + function fixGeckoDisappearingCursor() { + editor.once( 'afterPaste', function() { + editor.toolbox.focus(); + } ); } // Create object representing Cut or Copy commands. @@ -1397,9 +1358,107 @@ return data; } - // Copy of getRangeAtDropPosition method from widget plugin. - // In #11219 method in widget should be removed and everything be according to DRY. - function getRangeAtDropPosition( editor, dropEvt ) { + /** + * @private + * @singleton + * @class CKEDITOR.plugins.clipboard + */ + CKEDITOR.plugins.clipboard = {}; + + /** + * @private + * + * IE 8 & 9 split text node on drop so the first node contains + * text before drop position and the second contains rest. If we + * drag the content from the same node we will be not able to get + * it (range became invalid), so we need to join them back. + * + * Notify that first node on IE 8 & 9 is the original node object + * but with shortened content. + * + * Before: + * --- Text Node A ---------------------------------- + * /\ + * Drag position + * + * After (IE 8 & 9): + * --- Text Node A ----- --- Text Node B ----------- + * /\ /\ + * Drop position Drag position + * (invalid) + * + * After (other browsers): + * --- Text Node A ---------------------------------- + * /\ /\ + * Drop position Drag position + * + * This function is in the public scope for tests usage only. + */ + CKEDITOR.plugins.clipboard.fixIESplittedNodes = function( dragRange, dropRange ) { + if ( dropRange.startContainer.type == 1 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), + offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); + } + + dropRange.startContainer = nodeBefore; + dropRange.startOffset = offset; + } + }; + + /** + * @private + * + * Check if the beginning of the firstRange is before the beginning of the secondRange + * and modification of the content in the firstRange may break secondRange. + * + * Notify that this function returns false if these two ranges are in two + * separate nodes and do not affect each other (even if firstRange is before secondRange). + * + * This function is in the public scope for tests usage only. + */ + CKEDITOR.plugins.clipboard.rangeBefore = function( firstRange, secondRange ) { + // Both ranges has the same parent and the first has smaller offset. E.g.: + // + // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." + // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." + // + if ( firstRange.startContainer.equals( secondRange.startContainer ) && + firstRange.startOffset < secondRange.startOffset ) + return true; + + // First range is inside a text node and the second is not, but if we change the + // first range into bookmark and split the text node then the seconds node offset + // will be no longer correct. + // + // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." + // + if ( firstRange.startContainer.getParent().equals( secondRange.startContainer ) && + firstRange.startContainer.getIndex() < secondRange.startOffset ) + return true; + + return false; + }; + + /** + * Get range from the drop event. + * + * Copy of getRangeAtDropPosition method from widget plugin. + * In #11219 method in widget should be removed and everything be according to DRY. + * + * @param {CKEDITOR.editor} editor The source editor instance. + * @param {Object} domEvent A native DOM drop event object. + * + * @returns {CKEDITOR.dom.range} range at drop position. + * + */ + CKEDITOR.plugins.clipboard.getRangeAtDropPosition = function( editor, dropEvt ) { var $evt = dropEvt.data.$, x = $evt.clientX, y = $evt.clientY, @@ -1534,35 +1593,37 @@ return null; return range; - } + }; - // Check if the beginning of the firstRange is before the beginning of the secondRange - // and modification of the content in the firstRange may break secondRange. - // - // Notify that this function returns false if these two ranges are in two - // separate nodes and do not affect each other (even if firstRange is before secondRange). - function rangeBefore( firstRange, secondRange ) { - // Both ranges has the same parent and the first has smaller offset. E.g.: - // - // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." - // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." - // - if ( firstRange.startContainer.equals( secondRange.startContainer ) && - firstRange.startOffset < secondRange.startOffset ) - return true; + /** + * @private + * + * Initialize CKEDITOR.plugins.clipboard.dataTransfer object. + * + */ + CKEDITOR.plugins.clipboard.initDataTransfer = function( evt, sourceEditor, targetEditor ) { + // Create a new dataTransfer object based on the drop event. + // If this event was used on dragstart to create dataTransfer + // both dataTransfer objects will have the same id. + var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, sourceEditor ); + + // If there is the same id we will replace dataTransfer with the one + // created on drag, because it contains drag editor, drag content and so on. + // Otherwise (in case of drag from external source) we save new object to + // the global clipboard.dnd. + if ( CKEDITOR.plugins.clipboard.dnd && + dataTransfer.id == CKEDITOR.plugins.clipboard.dnd.id ) { + dataTransfer = CKEDITOR.plugins.clipboard.dnd; + } else { + CKEDITOR.plugins.clipboard.dnd = dataTransfer; + } - // First range is inside a text node and the second is not, but if we change the - // first range into bookmark and split the text node then the seconds node offset - // will be no longer correct. - // - // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." - // - if ( firstRange.startContainer.getParent().equals( secondRange.startContainer ) && - firstRange.startContainer.getIndex() < secondRange.startOffset ) - return true; + if ( targetEditor ) { + dataTransfer.setTargetEditor( targetEditor ); + } - return false; - } + return dataTransfer; + }; // Data type used to link drag and drop events. var clipboardIdDataType = @@ -1576,13 +1637,6 @@ // In Chrome and Firefox we can use custom data types. 'cke/id'; - /** - * @private - * @singleton - * @class CKEDITOR.plugins.clipboard - */ - CKEDITOR.plugins.clipboard = {}; - /** * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences * between browsers. From 421c0ac4f16e15cf335ec458fe3646ac90f3ad47 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 16:59:29 +0200 Subject: [PATCH 21/67] Move preventDefault so it is now the very first operation in the drop event handler. --- plugins/clipboard/plugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 6606c3d9c28..a556109b74b 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -551,15 +551,15 @@ } ); editable.attachListener( dropTarget, 'drop', function( evt ) { + // Cancel native drop. + evt.data.preventDefault(); + // Create dataTransfer of get it, if it was created before. var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, null, editor ); // Getting drop position is one of the most complex part of D&D. var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ); - // Cancel native drop. - evt.data.preventDefault(); - // Do nothing if it was not possible to get drop range. if ( !dropRange ) return; From 709394a6954bfad13c9940f6aa4d420a4c067967 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 18:19:59 +0200 Subject: [PATCH 22/67] Remove sourceRanges from dataTransfer object and use CKEDITOR.plugins.clipboard.dragRanges instead. --- plugins/clipboard/plugin.js | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index a556109b74b..84875df07c0 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -558,16 +558,17 @@ var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, null, editor ); // Getting drop position is one of the most complex part of D&D. - var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ); + var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ), + dragRanges = CKEDITOR.plugins.clipboard.dragRanges; // Do nothing if it was not possible to get drop range. if ( !dropRange ) return; if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { - internalDnD( dropRange, dataTransfer ); + internalDnD( dragRanges, dropRange, dataTransfer ); } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { - crossEditorDnD( dropRange, dataTransfer ); + crossEditorDnD( dragRanges, dropRange, dataTransfer ); } else { externalDnD( dropRange, dataTransfer ); } @@ -575,12 +576,11 @@ } // Internal D&D. - function internalDnD( dropRange, dataTransfer ) { + function internalDnD( dragRanges, dropRange, dataTransfer ) { // Execute D&D with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { - var dragRanges = dataTransfer.sourceRanges, - dragBookmarks = [], + var dragBookmarks = [], dragRange, dropBookmark, i, // Functions shortcuts. @@ -634,7 +634,7 @@ } // Cross editor D&D. - function crossEditorDnD( dropRange, dataTransfer ) { + function crossEditorDnD( dragRanges, dropRange, dataTransfer ) { var i; // Because of FF bug we need to use this hack, otherwise cursor is hidden. @@ -650,8 +650,8 @@ // Remove dragged content and make a snapshot. dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - for ( i = 0; i < dataTransfer.sourceRanges.length; i++ ) { - dataTransfer.sourceRanges[ i ].deleteContents(); + for ( i = 0; i < dragRanges.length; i++ ) { + dragRanges[ i ].deleteContents(); } dataTransfer.sourceEditor.getSelection().reset(); dataTransfer.sourceEditor.fire( 'saveSnapshot' ); @@ -1618,6 +1618,10 @@ CKEDITOR.plugins.clipboard.dnd = dataTransfer; } + if ( sourceEditor ) { + CKEDITOR.plugins.clipboard.dragRanges = sourceEditor.getSelection().getRanges(); + } + if ( targetEditor ) { dataTransfer.setTargetEditor( targetEditor ); } @@ -1682,7 +1686,6 @@ this.sourceEditor = editor; this.dataValue = editor.getSelection().getSelectedHtml(); this.dataType = 'html'; - this.sourceRanges = editor.getSelection().getRanges(); } else { // IE support only text data and throws exception if we try to get html data. // This html data object may also be empty if we drag content of the textarea. @@ -1824,14 +1827,6 @@ * @property {String} dataType * @member CKEDITOR.plugins.clipboard.dataTransfer */ - - /** - * Range instances that represent the selection during drag. - * - * @private - * @property {Array} sourceRanges - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ } )(); /** From 535e4c1038a338bb97afaf04d66b5509b54662d1 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 18:31:05 +0200 Subject: [PATCH 23/67] Remove support for multiple ranges. --- plugins/clipboard/plugin.js | 57 +++++++++++++++---------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 84875df07c0..90755361a94 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -559,16 +559,16 @@ // Getting drop position is one of the most complex part of D&D. var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ), - dragRanges = CKEDITOR.plugins.clipboard.dragRanges; + dragRange = CKEDITOR.plugins.clipboard.dragRange; // Do nothing if it was not possible to get drop range. if ( !dropRange ) return; if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { - internalDnD( dragRanges, dropRange, dataTransfer ); + internalDnD( dragRange, dropRange, dataTransfer ); } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { - crossEditorDnD( dragRanges, dropRange, dataTransfer ); + crossEditorDnD( dragRange, dropRange, dataTransfer ); } else { externalDnD( dropRange, dataTransfer ); } @@ -576,14 +576,11 @@ } // Internal D&D. - function internalDnD( dragRanges, dropRange, dataTransfer ) { + function internalDnD( dragRange, dropRange, dataTransfer ) { // Execute D&D with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { - var dragBookmarks = [], - dragRange, dropBookmark, i, - - // Functions shortcuts. + var dragBookmark, dropBookmark, i, rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore, fixIESplittedNodes = CKEDITOR.plugins.clipboard.fixIESplittedNodes; @@ -593,35 +590,26 @@ editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - fixIESplittedNodes( dragRanges[ 0 ], dropRange ); + fixIESplittedNodes( dragRange, dropRange ); } // Because we manipulate multiple ranges we need to do it carefully, // changing one range (event creating a bookmark) may make other invalid. - // We need to change ranges into bookmark so we can manipulate them easily in the future. + // We need to change ranges into bookmarks so we can manipulate them easily in the future. // We can change the range which is later in the text before we change the preceding range. // We call rangeBefore to test the order of ranges. - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmark = dragRange.createBookmark( 1 ); - var dropRangeCopy = dropRange.clone(); - dropBookmark = dropRangeCopy.createBookmark( 1 ); + dropBookmark = dropRange.clone().createBookmark( 1 ); - for ( i = 0; i < dragRanges.length; i++ ) { - dragRange = dragRanges[ i ]; - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmarks.push( dragRange.createBookmark( 1 ) ); - } + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmark = dragRange.createBookmark( 1 ); - // No we can safely delete content for the drag ranges... - for ( i = 0; i < dragBookmarks.length; i++ ) { - dragRange = editor.createRange(); - dragRange.moveToBookmark( dragBookmarks[ i ] ); - dragRange.deleteContents(); - } + // No we can safely delete content for the drag range... + dragRange = editor.createRange(); + dragRange.moveToBookmark( dragBookmark ); + dragRange.deleteContents(); // ...and paste content into the drop position. dropRange = editor.createRange(); @@ -634,7 +622,7 @@ } // Cross editor D&D. - function crossEditorDnD( dragRanges, dropRange, dataTransfer ) { + function crossEditorDnD( dragRange, dropRange, dataTransfer ) { var i; // Because of FF bug we need to use this hack, otherwise cursor is hidden. @@ -650,9 +638,9 @@ // Remove dragged content and make a snapshot. dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - for ( i = 0; i < dragRanges.length; i++ ) { - dragRanges[ i ].deleteContents(); - } + + dragRange.deleteContents(); + dataTransfer.sourceEditor.getSelection().reset(); dataTransfer.sourceEditor.fire( 'saveSnapshot' ); } @@ -1619,7 +1607,7 @@ } if ( sourceEditor ) { - CKEDITOR.plugins.clipboard.dragRanges = sourceEditor.getSelection().getRanges(); + CKEDITOR.plugins.clipboard.dragRange = sourceEditor.getSelection().getRanges()[ 0 ]; } if ( targetEditor ) { @@ -1652,7 +1640,8 @@ * A native DOM event object. * * @param {CKEDITOR.editor} editor The source editor instance. - * If this is set then html and sourceRanges will be created based on the editor contents. + * If editor is defined then dataValue will be created based on + * the editor contents and dataType will be 'html'. */ CKEDITOR.plugins.clipboard.dataTransfer = function( evt, editor ) { this.$ = evt.data.$.dataTransfer; From 4c9cf6c9467766147881ddfa862bcab296ed4dc1 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Tue, 24 Jun 2014 18:36:11 +0200 Subject: [PATCH 24/67] Changed order of arguments in getRangeAtDropPosition. --- plugins/clipboard/plugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 90755361a94..94ba61c6753 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -558,7 +558,7 @@ var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, null, editor ); // Getting drop position is one of the most complex part of D&D. - var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( editor, evt ), + var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), dragRange = CKEDITOR.plugins.clipboard.dragRange; // Do nothing if it was not possible to get drop range. @@ -1440,13 +1440,13 @@ * Copy of getRangeAtDropPosition method from widget plugin. * In #11219 method in widget should be removed and everything be according to DRY. * - * @param {CKEDITOR.editor} editor The source editor instance. * @param {Object} domEvent A native DOM drop event object. + * @param {CKEDITOR.editor} editor The source editor instance. * * @returns {CKEDITOR.dom.range} range at drop position. * */ - CKEDITOR.plugins.clipboard.getRangeAtDropPosition = function( editor, dropEvt ) { + CKEDITOR.plugins.clipboard.getRangeAtDropPosition = function( dropEvt, editor ) { var $evt = dropEvt.data.$, x = $evt.clientX, y = $evt.clientY, From ddcd0c3e1cd02e387ed66a5d9ec63774cdbd222b Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 11:48:21 +0200 Subject: [PATCH 25/67] Make initDataTransfer public and added docs. --- plugins/clipboard/plugin.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 94ba61c6753..6b6ed289c9f 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1584,9 +1584,14 @@ }; /** - * @private + * Initialize dataTransfer object based on the native drop event. If data + * transfer object was already initialized on this event then function will + * return that object. * - * Initialize CKEDITOR.plugins.clipboard.dataTransfer object. + * @param {Object} domEvent A native DOM drop event object. + * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. + * @param {CKEDITOR.editor} [targetEditor] The target editor instance. + * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object * */ CKEDITOR.plugins.clipboard.initDataTransfer = function( evt, sourceEditor, targetEditor ) { From 3a83147e29d9acdf3560a1bcf12e4855ef8acf58 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 11:50:40 +0200 Subject: [PATCH 26/67] Removed target editor from initDataTransfer. --- plugins/clipboard/plugin.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 6b6ed289c9f..c476e5830cb 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -555,7 +555,8 @@ evt.data.preventDefault(); // Create dataTransfer of get it, if it was created before. - var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, null, editor ); + var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt ); + dataTransfer.setTargetEditor( editor ); // Getting drop position is one of the most complex part of D&D. var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), @@ -1590,11 +1591,10 @@ * * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. - * @param {CKEDITOR.editor} [targetEditor] The target editor instance. * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object * */ - CKEDITOR.plugins.clipboard.initDataTransfer = function( evt, sourceEditor, targetEditor ) { + CKEDITOR.plugins.clipboard.initDataTransfer = function( evt, sourceEditor ) { // Create a new dataTransfer object based on the drop event. // If this event was used on dragstart to create dataTransfer // both dataTransfer objects will have the same id. @@ -1615,10 +1615,6 @@ CKEDITOR.plugins.clipboard.dragRange = sourceEditor.getSelection().getRanges()[ 0 ]; } - if ( targetEditor ) { - dataTransfer.setTargetEditor( targetEditor ); - } - return dataTransfer; }; From 5da610876d030438df8590c4b2cf686ad5e8f2ba Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 14:26:12 +0200 Subject: [PATCH 27/67] Moved Safari hack to the dataTransfer constructor. --- plugins/clipboard/plugin.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index c476e5830cb..aa29c63e592 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -536,11 +536,6 @@ editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it to the global clipboard.dnd. CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); - - // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. - if ( CKEDITOR.env.safari ) { - evt.data.$.dataTransfer.setData( 'text', evt.data.$.dataTransfer.getData( 'text' ) ); - } } ); // Clean up on dragend. @@ -1672,6 +1667,11 @@ this.$.setData( clipboardIdDataType, this.id ); } + // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. + if ( evt.name == 'dragstart' && CKEDITOR.env.safari ) { + evt.data.$.dataTransfer.setData( 'text', evt.data.$.dataTransfer.getData( 'text' ) ); + } + if ( editor ) { this.sourceEditor = editor; this.dataValue = editor.getSelection().getSelectedHtml(); From 8cba450aa983efb149a1fff1b6542a752107f468 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 14:39:35 +0200 Subject: [PATCH 28/67] Introduced resetDataTransfer method. --- plugins/clipboard/plugin.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index aa29c63e592..7ed71d09bdd 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -534,15 +534,15 @@ // Listed on dragstart to mark internal and cross-editor D&D // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { - // Create a dataTransfer object and save it to the global clipboard.dnd. + // Create a dataTransfer object and save it globally. CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); } ); // Clean up on dragend. editable.attachListener( dropTarget, 'dragend', function( evt ) { - // When DnD is done we need to remove clipboard.dnd so the future + // When DnD is done we need to reset dataTransfer so the future // external drop will be not recognize as internal. - CKEDITOR.plugins.clipboard.dnd = undefined; + CKEDITOR.plugins.clipboard.resetDataTransfer(); } ); editable.attachListener( dropTarget, 'drop', function( evt ) { @@ -1613,6 +1613,14 @@ return dataTransfer; }; + /* + * Remove global dataTransfer object so the new dataTransfer + * will be not linked with the old one. + */ + CKEDITOR.plugins.clipboard.resetDataTransfer = function() { + CKEDITOR.plugins.clipboard.dnd = undefined; + }; + // Data type used to link drag and drop events. var clipboardIdDataType = // IE does not support different data types that Text and URL. From 55d9ca70d89fafbc62abc3edefffbce015a44cab Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 15:19:58 +0200 Subject: [PATCH 29/67] Removed getRangeAtDropPosition from widget plugin. Used CKEDITOR.plugins.clipboard.getRangeAtDropPosition instead. --- plugins/widget/plugin.js | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/plugins/widget/plugin.js b/plugins/widget/plugin.js index 1e4d323c16d..8e19b647bdd 100644 --- a/plugins/widget/plugin.js +++ b/plugins/widget/plugin.js @@ -2105,43 +2105,6 @@ editor.fire( 'unlockSnapshot' ); } - function getRangeAtDropPosition( editor, dropEvt ) { - var $evt = dropEvt.data.$, - $range, - range = editor.createRange(); - - // Make testing possible. - if ( dropEvt.data.testRange ) - return dropEvt.data.testRange; - - // Webkits. - if ( document.caretRangeFromPoint ) { - $range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY ); - range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); - range.collapse( true ); - } - // FF. - else if ( $evt.rangeParent ) { - range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); - range.collapse( true ); - } - // IEs. - else if ( document.body.createTextRange ) { - $range = editor.document.getBody().$.createTextRange(); - $range.moveToPoint( $evt.clientX, $evt.clientY ); - var id = 'cke-temp-' + ( new Date() ).getTime(); - $range.pasteHTML( '\u200b' ); - - var span = editor.document.getById( id ); - range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); - span.remove(); - } - else - return null; - - return range; - } - function onEditableKey( widget, keyCode ) { var focusedEditable = widget.focusedEditable, range; @@ -2271,7 +2234,7 @@ // Try to determine a DOM position at which drop happened. If none of methods // which we support succeeded abort. - range = getRangeAtDropPosition( editor, evt ); + range = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ); if ( !range ) return; From 58a1bea6737601ad6fd6594f9dc6477fe784a4f2 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 25 Jun 2014 17:42:00 +0200 Subject: [PATCH 30/67] Modified tests so they are based on range instead on points now. --- tests/plugins/clipboard/drop.js | 146 ++++++++------------------------ 1 file changed, 37 insertions(+), 109 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 119570068c8..1d71b782f29 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -34,38 +34,21 @@ function drag( editor, evt ) { } function drop( editor, evt, config, callback ) { - // document.caretRangeFromPoint returns null - // for inline and divbased editor if viewport is too small. - if ( CKEDITOR.env.webkit && editor.name != 'framed' && window.innerHeight < 300 ) - assert.ignore(); - - // IE8 also has some problem with running test if the window is too small. - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && editor.name != 'framed' && document.documentElement.clientHeight < 500 ) - assert.ignore(); - var editable = editor.editable(), dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, range = new CKEDITOR.dom.range( editor.document ), pasteEventCounter = 0, - expectedPasteEventCount = typeof config.expectedPasteEventCount !== 'undefined' ? config.expectedPasteEventCount : 1, - x = config.x[ editor.name ] ? config.x[ editor.name ] : config.x, - y = config.y[ editor.name ] ? config.y[ editor.name ] : config.y; + expectedPasteEventCount = typeof config.expectedPasteEventCount !== 'undefined' ? + config.expectedPasteEventCount : + 1; - // To simulate drop event we need to set selection (IE use it), - // clientX and clientY (IE and Chrome use it), - // rangeParent and rangeOffset (FF use it). range.setStart( config.element, config.offset ); range.collapse( true ); range.select(); editor.focus(); - evt.$.clientX = x; - evt.$.clientY = y; - if ( CKEDITOR.env.gecko ) { - evt.$.rangeParent = config.element.$; - evt.$.rangeOffset = config.offset; - } + evt.testRange = range; editor.on( 'paste', function() { pasteEventCounter++; @@ -80,67 +63,46 @@ function drop( editor, evt, config, callback ) { }, 100 ); } -var logViewTopBackup, - editorsDefinitions = { - framed: { - name: 'framed', - creator: 'replace', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - }, - inline: { - name: 'inline', - creator: 'inline', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - }, - divarea: { - name: 'divarea', - creator: 'replace', - config: { - extraPlugins: 'divarea', - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - }, - cross: { - name: 'cross', - creator: 'replace', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } +var editorsDefinitions = { + framed: { + name: 'framed', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] } }, - contentsFrame = CKEDITOR.env.webkit && CKEDITOR.document && CKEDITOR.document.getWindow().$.frameElement; - - contentsFrame && ( contentsFrame.style.width = '1%' ); - contentsFrame && ( contentsFrame.style.width = '1000px' ); + inline: { + name: 'inline', + creator: 'inline', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + divarea: { + name: 'divarea', + creator: 'replace', + config: { + extraPlugins: 'divarea', + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + cross: { + name: 'cross', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + } +}; bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { bender.test( bender.tools.createTestsForEditors( [ editors.framed, editors.inline, editors.divarea ], { - setUp: function() { - CKEDITOR.document.getById( 'framed-container' ).hide(); - CKEDITOR.document.getById( 'divarea-container' ).hide(); - CKEDITOR.document.getById( 'inline-container' ).hide(); - - logViewTopBackup = CKEDITOR.document.findOne( '.results' ).getStyle( 'top' ); - CKEDITOR.document.findOne( '.results' ).setStyle( 'top', 'auto' ); - }, - - tearDown: function() { - - CKEDITOR.document.findOne( '.results' ).setStyle( 'top', logViewTopBackup ); - }, - 'test drop to header': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -150,8 +112,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editor, evt ); drop( editor, evt, { - x: 260, - y: { framed: 33, inline: 111, divarea: 172 }, element: editor.document.getById( 'h1' ).getChild( 0 ), offset: 7 }, function() { @@ -164,8 +124,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop the same line, before': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -174,8 +132,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editor, evt ); drop( editor, evt, { - x: { framed: 60, inline: 45, divarea: 54 }, - y: { framed: 131, inline: 112, divarea: 216 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { @@ -188,8 +144,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop the same line, after': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -198,8 +152,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editor, evt ); drop( editor, evt, { - x: { framed: 151, inline: ( CKEDITOR.env.webkit ? 142 : 139 ), divarea: 151 }, - y: { framed: 38, inline: 112, divarea: 224 }, element: editor.document.getById( 'p' ).getChild( 2 ), offset: 11 }, function() { @@ -212,8 +164,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop after paragraph': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -222,8 +172,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editor, evt ); drop( editor, evt, { - x: { framed: 251, inline: 240, divarea: 251 }, - y: { framed: 38, inline: 112, divarea: 224 }, element: editor.document.getById( 'p' ).getChild( 2 ), offset: 16 }, function() { @@ -236,8 +184,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop on the left from paragraph': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -246,8 +192,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editor, evt ); drop( editor, evt, { - x: 15, - y: { framed: 38, inline: 112, divarea: 224 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 0 }, function() { @@ -260,8 +204,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop from external source': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -273,8 +215,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { evt.$.dataTransfer.setData( 'text/html', 'dolor' ); drop( editor, evt, { - x: { framed: 60, inline: 45, divarea: 54 }, - y: { framed: 131, inline: 112, divarea: 216 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { @@ -287,8 +227,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop html from external source': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -300,8 +238,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { evt.$.dataTransfer.setData( 'text/html', 'dolor' ); drop( editor, evt, { - x: { framed: 60, inline: 45, divarea: 54 }, - y: { framed: 131, inline: 112, divarea: 216 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { @@ -314,16 +250,12 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test drop empty element from external source': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(); bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); drop( editor, evt, { - x: { framed: 60, inline: 45, divarea: 54 }, - y: { framed: 131, inline: 112, divarea: 216 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6, expectedPasteEventCount: 0 @@ -333,8 +265,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { }, 'test cross editor drop': function( editor ) { - CKEDITOR.document.getById( editor.name + '-container' ).show(); - var bot = editorBots[ editor.name ], evt = createDnDEventMock(), botCross = editorBots[ 'cross' ], @@ -346,8 +276,6 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { drag( editorCross, evt ); drop( editor, evt, { - x: { framed: 60, inline: 45, divarea: 54 }, - y: { framed: 131, inline: 112, divarea: 216 }, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { From 35049f24e263817d46ebc9bfd7ce323cf26c9fcc Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 26 Jun 2014 15:11:25 +0200 Subject: [PATCH 31/67] Small fixes in the fixIESplittedNodes method. --- plugins/clipboard/plugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 7ed71d09bdd..8ba5594d98b 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1379,7 +1379,7 @@ * This function is in the public scope for tests usage only. */ CKEDITOR.plugins.clipboard.fixIESplittedNodes = function( dragRange, dropRange ) { - if ( dropRange.startContainer.type == 1 && + if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), @@ -1391,8 +1391,8 @@ nodeAfter.remove(); } - dropRange.startContainer = nodeBefore; - dropRange.startOffset = offset; + dropRange.setStart( nodeBefore, offset ); + dropRange.collapse( true ); } }; From 773a00d84761845419d7356c5a4f4fbd939cf444 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 26 Jun 2014 15:11:51 +0200 Subject: [PATCH 32/67] Tests: fixIESplittedNodes. --- tests/plugins/clipboard/drop.js | 119 ++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 38 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 1d71b782f29..d0616dce356 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -63,45 +63,43 @@ function drop( editor, evt, config, callback ) { }, 100 ); } -var editorsDefinitions = { - framed: { - name: 'framed', - creator: 'replace', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - }, - inline: { - name: 'inline', - creator: 'inline', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - }, - divarea: { - name: 'divarea', - creator: 'replace', - config: { - extraPlugins: 'divarea', - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] +var editors, editorBots, + editorsDefinitions = { + framed: { + name: 'framed', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + inline: { + name: 'inline', + creator: 'inline', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + divarea: { + name: 'divarea', + creator: 'replace', + config: { + extraPlugins: 'divarea', + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } + }, + cross: { + name: 'cross', + creator: 'replace', + config: { + allowedContent: true, + toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + } } }, - cross: { - name: 'cross', - creator: 'replace', - config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] - } - } -}; - -bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { - bender.test( bender.tools.createTestsForEditors( - [ editors.framed, editors.inline, editors.divarea ], { + testsForMultipleEditor = { 'test drop to header': function( editor ) { var bot = editorBots[ editor.name ], evt = createDnDEventMock(); @@ -290,5 +288,50 @@ bender.tools.setUpEditors( editorsDefinitions, function( editors, editorBots ) { assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editorCross.getData(), 0, 1, 0, 1 ), 'after undo - editor cross' ); } ); } - } ) ); + }, + testsForOneEditor = { + 'test fixIESplittedNodes': function() { + var editor = editors.framed, + bot = editorBots[ editor.name ], + dragRange = editor.createRange(), + dropRange = editor.createRange(), + p, text; + + // Create DOM + bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + p = editor.document.getById( 'p' ); + + // Set drag range. + dragRange.setStart( p.getChild( 0 ), 11 ); + dragRange.collapse( true ); + + // Break content like IE do. + p.getChild( 0 ).setText( 'Lorem' ); + text = new CKEDITOR.dom.text( ' ipsum sit amet.' ); + text.insertAfter( p.getChild( 0 ) ); + + // Set drop range. + dropRange.setStart( p, 1 ); + dropRange.collapse( true ); + + // Fix nodes. + CKEDITOR.plugins.clipboard.fixIESplittedNodes( dragRange, dropRange ); + + // Assert. + assert.areSame( 1, p.getChildCount() ); + dragRange.select(); + assert.areSame( '

Lorem ipsum{} sit amet.

', bender.tools.selection.getWithHtml( editor ) ); + } + }; + +bender.tools.setUpEditors( editorsDefinitions, function( e, eb ) { + editors = e; + editorBots = eb; + + bender.test( CKEDITOR.tools.extend( + bender.tools.createTestsForEditors( + [ editors.framed, editors.inline, editors.divarea ], + testsForMultipleEditor ), + testsForOneEditor ) + ); } ); \ No newline at end of file From 1ec1e8cf6b178d33d042ea4fae03fce07cfc4faf Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 26 Jun 2014 15:38:28 +0200 Subject: [PATCH 33/67] Tests: rangeBefore. --- tests/plugins/clipboard/drop.js | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index d0616dce356..9b972244b89 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -317,10 +317,78 @@ var editors, editorBots, // Fix nodes. CKEDITOR.plugins.clipboard.fixIESplittedNodes( dragRange, dropRange ); - // Assert. + // Asserts. assert.areSame( 1, p.getChildCount() ); dragRange.select(); assert.areSame( '

Lorem ipsum{} sit amet.

', bender.tools.selection.getWithHtml( editor ) ); + dropRange.select(); + assert.areSame( '

Lorem{} ipsum sit amet.

', bender.tools.selection.getWithHtml( editor ) ); + }, + + 'test rangeBefore 1': function() { + var editor = editors.framed, + bot = editorBots[ editor.name ], + firstRange = editor.createRange(), + secondRange = editor.createRange(), + p; + + // "Lorem[1] ipsum[2] sit amet." + bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + p = editor.document.getById( 'p' ); + + firstRange.setStart( p.getChild( 0 ), 5 ); + firstRange.collapse( true ); + + secondRange.setStart( p.getChild( 0 ), 11 ); + secondRange.collapse( true ); + + assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); + }, + + 'test rangeBefore 2': function() { + var editor = editors.framed, + bot = editorBots[ editor.name ], + firstRange = editor.createRange(), + secondRange = editor.createRange(), + p, text; + + // "Lorem " [1] " ipsum" [2] "sit amet." + bot.setHtmlWithSelection( '

Lorem

' ); + p = editor.document.getById( 'p' ); + text = new CKEDITOR.dom.text( ' ipsum' ); + text.insertAfter( p.getChild( 0 ) ); + text = new CKEDITOR.dom.text( ' sit amet.' ); + text.insertAfter( p.getChild( 0 ) ); + + firstRange.setStart( p, 1 ); + firstRange.collapse( true ); + + secondRange.setStart( p, 2 ); + secondRange.collapse( true ); + + assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); + }, + + 'test rangeBefore 3': function() { + var editor = editors.framed, + bot = editorBots[ editor.name ], + firstRange = editor.createRange(), + secondRange = editor.createRange(), + p, text; + + // "Lorem[1] ipsum" [2] "sit amet." + bot.setHtmlWithSelection( '

Lorem ipsum

' ); + p = editor.document.getById( 'p' ); + text = new CKEDITOR.dom.text( ' sit amet.' ); + text.insertAfter( p.getChild( 0 ) ); + + firstRange.setStart( p.getChild( 0 ), 5 ); + firstRange.collapse( true ); + + secondRange.setStart( p, 1 ); + secondRange.collapse( true ); + + assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); } }; From 85d4f3f5afe3dac3f9cafe757638e63551fbdf59 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 26 Jun 2014 16:03:44 +0200 Subject: [PATCH 34/67] Tests: We do not need width and height in body anymore. --- tests/plugins/clipboard/drop.html | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/plugins/clipboard/drop.html b/tests/plugins/clipboard/drop.html index 1d08d227bf7..d3ca18a8b2f 100644 --- a/tests/plugins/clipboard/drop.html +++ b/tests/plugins/clipboard/drop.html @@ -1,14 +1,12 @@ - -
-
- -
-
- -
-
-
-
- +
+
+
- \ No newline at end of file +
+ +
+
+
+
+ +
\ No newline at end of file From 1e8af6e3108c208a362f9bcae6b7e727971a8b52 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 14:31:02 +0200 Subject: [PATCH 35/67] Moved unique ID generation to the separate function. --- plugins/clipboard/plugin.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 8ba5594d98b..16a18619138 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1653,11 +1653,15 @@ // Check if ID is already created. this.id = this.$.getData( clipboardIdDataType ); + function generateUniqueId() { + return ( new Date() ).getTime() + Math.random().toString( 16 ).substring( 2 ) + } + // If there is no ID we need to create it. Different browsers needs different ID. if ( !this.id ) { if ( clipboardIdDataType == 'URL' ) { // For IEs URL type ID have to look like an URL. - this.id = 'http://cke.' + ( new Date() ).getTime() +'/'; + this.id = 'http://cke.' + generateUniqueId() +'/'; } else if ( clipboardIdDataType == 'Text' ) { // For IE10+ only Text data type is supported and we have to compare dragged // and dropped text. If the ID is not set it means that empty string was dragged @@ -1665,7 +1669,7 @@ this.id = ""; } else { // String for custom data type. - this.id = 'cke-' + ( new Date() ).getTime(); + this.id = 'cke-' + generateUniqueId(); } } From 78a425cd0ec4533270d6c55b8510e0400e30f842 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 14:32:39 +0200 Subject: [PATCH 36/67] Added datatransfer tests. --- tests/plugins/clipboard/datatransfer.js | 206 ++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 tests/plugins/clipboard/datatransfer.js diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js new file mode 100644 index 00000000000..19d1853a7c2 --- /dev/null +++ b/tests/plugins/clipboard/datatransfer.js @@ -0,0 +1,206 @@ +/* bender-tags: editor,unit */ +/* bender-ckeditor-plugins: toolbar,clipboard */ + +'use strict'; + +function createDnDEventMock() { + return { + data: { + $: { + dataTransfer: { + _dataTypes : [], + // Emulate browsers native behavior for getDeta/setData. + setData: function( type, data ) { + if ( CKEDITOR.env.ie && type != 'Text' && type != 'URL' ) + throw "Unexpected call to method or property access."; + + if ( CKEDITOR.env.ie && CKEDITOR.env.version > 9 && type == 'URL' ) + return; + + this._dataTypes[ type ] = data; + }, + getData: function( type ) { + if ( CKEDITOR.env.ie && type != 'Text' && type != 'URL' ) + throw "Invalid argument."; + + if ( !this._dataTypes[ type ] ) + return ''; + + return this._dataTypes[ type ]; + }, + } + } + } + } +} + +bender.test( { + 'async:init': function() { + var that = this; + + bender.tools.setUpEditors( { + editor1: { + name: 'editor1' + }, + editor2: { + name: 'editor2' + } + }, function( editors, bots ) { + that.bots = bots; + that.editors = editors; + + that.callback(); + } ); + }, + + 'test dataTransfer id': function() { + var evt1 = createDnDEventMock(), + evt2 = createDnDEventMock(), + dataTransfer1a = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), + dataTransfer1b = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), + dataTransfer2 = new CKEDITOR.plugins.clipboard.dataTransfer( evt2 ); + + assert.areSame( dataTransfer1a.id, dataTransfer1b.id ); + + // In IE10+ we can not use any data type besides text, so id is fixed. + if ( !CKEDITOR.env.ie || CKEDITOR.env.version < 10 ) + assert.areNotSame( dataTransfer1a.id, dataTransfer2.id ); + }, + + 'test dataTransfer internal': function() { + var bot = this.bots.editor1, + editor = this.editors.editor1, + evt, dataTransfer; + + bot.setHtmlWithSelection( '[foo]' ); + + evt = createDnDEventMock(); + evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); + dataTransfer.setTargetEditor( editor ); + + assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); + assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); + assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + + }, + + 'test dataTransfer external text': function() { + var editor = this.editors.editor1, + evt, dataTransfer; + + evt = createDnDEventMock(); + evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); + dataTransfer.setTargetEditor( editor ); + + assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); + assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); + assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + }, + + 'test dataTransfer external html': function() { + var editor = this.editors.editor1, + evt, dataTransfer; + + evt = createDnDEventMock(); + evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + if ( !CKEDITOR.env.ie ) { + evt.data.$.dataTransfer.setData( 'text/html', 'foo' ); + } + + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); + dataTransfer.setTargetEditor( editor ); + + assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); + assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); + assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); + + if ( CKEDITOR.env.ie ) { + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + } else { + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + assert.areSame( 'foo', dataTransfer.getData( 'text/html' ), 'getData( \'text/html\' )' ); + } + }, + + 'test dataTransfer cross': function() { + var bot1 = this.bots.editor1, + editor1 = this.editors.editor1, + editor2 = this.editors.editor2, + evt, dataTransfer; + + bot1.setHtmlWithSelection( '[foo]' ); + + evt = createDnDEventMock(); + evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor1 ); + dataTransfer.setTargetEditor( editor2 ); + + assert.areSame( CKEDITOR.DATA_TRANSFER_CROSS_EDITORS, dataTransfer.getTransferType() ); + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); + assert.areSame( editor1, dataTransfer.sourceEditor, 'sourceEditor' ); + assert.areSame( editor2, dataTransfer.targetEditor, 'targetEditor' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + }, + + 'test setData getData': function() { + var evt = createDnDEventMock(), + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); + + dataTransfer.setData( 'Text', 'foo' ); + + assert.areSame( 'foo', dataTransfer.getData( 'Text' ) ); + + }, + + 'test initDataTransfer binding': function() { + var evt1 = createDnDEventMock(), + evt2 = createDnDEventMock(), + dataTransferA = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ), + dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ); + + assert.areSame( dataTransferA, dataTransferB ); + + CKEDITOR.plugins.clipboard.resetDataTransfer(); + + dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt2 ); + + assert.areNotSame( dataTransferA, dataTransferB ); + + CKEDITOR.plugins.clipboard.resetDataTransfer(); + }, + + 'test initDataTransfer constructor': function() { + var bot = this.bots.editor1, + editor = this.editors.editor1; + + bot.setHtmlWithSelection( '[foo]' ); + + var evt = createDnDEventMock(), + dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); + dataTransfer.setTargetEditor( editor ); + + assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); + assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); + assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); + assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); + + CKEDITOR.plugins.clipboard.resetDataTransfer(); + } +} ); \ No newline at end of file From 9c1ee0d05957c07ccb835009d9c86ac4b776a4ca Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 14:56:19 +0200 Subject: [PATCH 37/67] Fixed small code style issues. --- plugins/clipboard/plugin.js | 14 +++++++------- tests/plugins/clipboard/drop.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 16a18619138..815ee72317e 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1380,8 +1380,8 @@ */ CKEDITOR.plugins.clipboard.fixIESplittedNodes = function( dragRange, dropRange ) { if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), offset = nodeBefore.getLength(); @@ -1491,7 +1491,7 @@ // // So we try to call moveToPoint with +-1px up to +-20px above or // below original drop position to find nearest good drop position. - for ( var i = 0; i < 20 && !sucess; i++ ) { + for ( var i = 0; i < 20 && !sucess; i++ ) { if ( !sucess ) { try { $range.moveToPoint( x, y - i ); @@ -1559,8 +1559,8 @@ // // In such case we can try to use default selection. If startContainer is not // 'editable' element it is probably proper selection. - else if ( defaultRange && defaultRange.startContainer && !defaultRange. - startContainer.equals( editor.editable() ) ) { + else if ( defaultRange && defaultRange.startContainer && + !defaultRange.startContainer.equals( editor.editable() ) ) { return defaultRange; } // Otherwise we can not find any drop position and we have to return null @@ -1622,7 +1622,7 @@ }; // Data type used to link drag and drop events. - var clipboardIdDataType = + var clipboardIdDataType = // IE does not support different data types that Text and URL. // In IE 9- we can use URL data type to mark that drag comes from the editor. ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) ? 'URL': @@ -1666,7 +1666,7 @@ // For IE10+ only Text data type is supported and we have to compare dragged // and dropped text. If the ID is not set it means that empty string was dragged // (ex. image with no alt). We change null to empty string. - this.id = ""; + this.id = ''; } else { // String for custom data type. this.id = 'cke-' + generateUniqueId(); diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 9b972244b89..bd1e6a445a8 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -6,7 +6,7 @@ CKEDITOR.disableAutoInline = true; function createDnDEventMock() { - return { + return { $: { clientX: 0, clientY: 0, @@ -24,7 +24,7 @@ function createDnDEventMock() { // noop } } - } +} function drag( editor, evt ) { var editable = editor.editable(), From 10e11132edf2e4ad62d6c739351fa138663ae558 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 15:20:48 +0200 Subject: [PATCH 38/67] Fixed and improve documentation. --- plugins/clipboard/plugin.js | 97 ++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 815ee72317e..65d41a8f0c5 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1343,7 +1343,6 @@ } /** - * @private * @singleton * @class CKEDITOR.plugins.clipboard */ @@ -1438,7 +1437,6 @@ * * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} editor The source editor instance. - * * @returns {CKEDITOR.dom.range} range at drop position. * */ @@ -1640,12 +1638,8 @@ * @class CKEDITOR.plugins.clipboard.dataTransfer * @constructor Creates a class instance and . * - * @param {Object} domEvent - * A native DOM event object. - * - * @param {CKEDITOR.editor} editor The source editor instance. - * If editor is defined then dataValue will be created based on - * the editor contents and dataType will be 'html'. + * @param {Object} domEvent A native DOM event object. + * @param {CKEDITOR.editor} editor The source editor instance. If editor is defined then dataValue will be created based on the editor contents and dataType will be 'html'. */ CKEDITOR.plugins.clipboard.dataTransfer = function( evt, editor ) { this.$ = evt.data.$.dataTransfer; @@ -1709,14 +1703,50 @@ } }; + /** + * Data transfer ID used to bind all dataTransfer + * object based on the same event (ex. in drag and drop events). + * + * @property {String} id + */ + + /** + * A native DOM event object. + * + * @private + * @property {Object} $ + */ + + /** + * Source editor, the editor where drag starts. + * Might be undefined if drag starts outside the editor (ex. dropping files to the editor). + * + * @property {CKEDITOR.editor} [sourceEditor] + */ + + /** + * Target editor, the editor where drop occurred. + * + * @property {CKEDITOR.editor} targetEditor + */ + + /** + * HTML or text to be pasted. + * + * @property {String} dataValue + */ + + /** + * Type of data in `data.dataValue`. The value might be `html` or `text`. + * + * @property {String} dataType + */ + /** * Facade for the native getData method. * * @param {String} type The type of data to retrieve. - * - * @returns {String} type - * Stored data for the given type or an - * empty string if data for that type does not exist. + * @returns {String} type Stored data for the given type or an empty string if data for that type does not exist. */ CKEDITOR.plugins.clipboard.dataTransfer.prototype.getData = function( type ) { return this.$.getData( type ); @@ -1786,49 +1816,6 @@ return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS; } }; - - /** - * ID - * - * @property {String} id - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ - - /** - * $ - * - * @private - * @property {Object} $ - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ - - /** - * sourceEditor - * - * @property {CKEDITOR.editor} sourceEditor - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ - - /** - * targetEditor - * - * @property {CKEDITOR.editor} targetEditor - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ - - /** - * dataValue - * - * @property {String} dataValue - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ - - /** - * dataType - * - * @property {String} dataType - * @member CKEDITOR.plugins.clipboard.dataTransfer - */ } )(); /** From f5104bfb0876eebdbaea180cc59e58f58f630d78 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 15:41:19 +0200 Subject: [PATCH 39/67] Stop using "dnd" and "d&d" names in code and documentation. --- plugins/clipboard/plugin.js | 40 ++++++++++++------------- tests/plugins/clipboard/datatransfer.js | 22 +++++++------- tests/plugins/clipboard/drop.js | 20 ++++++------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 65d41a8f0c5..c18095d7483 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -531,7 +531,7 @@ // #11086 IE8 cannot listen on document. var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; - // Listed on dragstart to mark internal and cross-editor D&D + // Listed on dragstart to mark internal and cross-editor drag & drop // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it globally. @@ -540,7 +540,7 @@ // Clean up on dragend. editable.attachListener( dropTarget, 'dragend', function( evt ) { - // When DnD is done we need to reset dataTransfer so the future + // When drag & drop is done we need to reset dataTransfer so the future // external drop will be not recognize as internal. CKEDITOR.plugins.clipboard.resetDataTransfer(); } ); @@ -553,7 +553,7 @@ var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt ); dataTransfer.setTargetEditor( editor ); - // Getting drop position is one of the most complex part of D&D. + // Getting drop position is one of the most complex part. var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), dragRange = CKEDITOR.plugins.clipboard.dragRange; @@ -562,18 +562,18 @@ return; if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { - internalDnD( dragRange, dropRange, dataTransfer ); + internalDrop( dragRange, dropRange, dataTransfer ); } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { - crossEditorDnD( dragRange, dropRange, dataTransfer ); + crossEditorDrop( dragRange, dropRange, dataTransfer ); } else { - externalDnD( dropRange, dataTransfer ); + externalDrop( dropRange, dataTransfer ); } } ); } - // Internal D&D. - function internalDnD( dragRange, dropRange, dataTransfer ) { - // Execute D&D with a timeout because otherwise selection, after drop, + // Internal drag and drop (drag and drop in the same Editor). + function internalDrop( dragRange, dropRange, dataTransfer ) { + // Execute drop with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { var dragBookmark, dropBookmark, i, @@ -617,8 +617,8 @@ }, 0 ); } - // Cross editor D&D. - function crossEditorDnD( dragRange, dropRange, dataTransfer ) { + // Cross editor drag and drop (drag in one Editor and drop in the other). + function crossEditorDrop( dragRange, dropRange, dataTransfer ) { var i; // Because of FF bug we need to use this hack, otherwise cursor is hidden. @@ -642,7 +642,7 @@ } // Drop from external source. - function externalDnD( dropRange, dataTransfer ) { + function externalDrop( dropRange, dataTransfer ) { // Because of FF bug we need to use this hack, otherwise cursor is hidden. if ( CKEDITOR.env.gecko ) { fixGeckoDisappearingCursor( editor ); @@ -1562,7 +1562,7 @@ return defaultRange; } // Otherwise we can not find any drop position and we have to return null - // and cancel D&D event. + // and cancel drop event. else return null; @@ -1596,12 +1596,12 @@ // If there is the same id we will replace dataTransfer with the one // created on drag, because it contains drag editor, drag content and so on. // Otherwise (in case of drag from external source) we save new object to - // the global clipboard.dnd. - if ( CKEDITOR.plugins.clipboard.dnd && - dataTransfer.id == CKEDITOR.plugins.clipboard.dnd.id ) { - dataTransfer = CKEDITOR.plugins.clipboard.dnd; + // the global clipboard.dragData. + if ( CKEDITOR.plugins.clipboard.dragData && + dataTransfer.id == CKEDITOR.plugins.clipboard.dragData.id ) { + dataTransfer = CKEDITOR.plugins.clipboard.dragData; } else { - CKEDITOR.plugins.clipboard.dnd = dataTransfer; + CKEDITOR.plugins.clipboard.dragData = dataTransfer; } if ( sourceEditor ) { @@ -1616,7 +1616,7 @@ * will be not linked with the old one. */ CKEDITOR.plugins.clipboard.resetDataTransfer = function() { - CKEDITOR.plugins.clipboard.dnd = undefined; + CKEDITOR.plugins.clipboard.dragData = null; }; // Data type used to link drag and drop events. @@ -1624,7 +1624,7 @@ // IE does not support different data types that Text and URL. // In IE 9- we can use URL data type to mark that drag comes from the editor. ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) ? 'URL': - // In IE 10+ URL data type is buggie and there is no way to mark DnD without + // In IE 10+ URL data type is buggie and there is no way to mark drag & drop without // modifying text data (which would be displayed if user drop content to the textarea) // so we just read dragged text. CKEDITOR.env.ie ? 'Text' : diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 19d1853a7c2..11ae4bb7b70 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -3,7 +3,7 @@ 'use strict'; -function createDnDEventMock() { +function createDragDropEventMock() { return { data: { $: { @@ -54,8 +54,8 @@ bender.test( { }, 'test dataTransfer id': function() { - var evt1 = createDnDEventMock(), - evt2 = createDnDEventMock(), + var evt1 = createDragDropEventMock(), + evt2 = createDragDropEventMock(), dataTransfer1a = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), dataTransfer1b = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), dataTransfer2 = new CKEDITOR.plugins.clipboard.dataTransfer( evt2 ); @@ -74,7 +74,7 @@ bender.test( { bot.setHtmlWithSelection( '[foo]' ); - evt = createDnDEventMock(); + evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); @@ -93,7 +93,7 @@ bender.test( { var editor = this.editors.editor1, evt, dataTransfer; - evt = createDnDEventMock(); + evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); @@ -111,7 +111,7 @@ bender.test( { var editor = this.editors.editor1, evt, dataTransfer; - evt = createDnDEventMock(); + evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); if ( !CKEDITOR.env.ie ) { evt.data.$.dataTransfer.setData( 'text/html', 'foo' ); @@ -144,7 +144,7 @@ bender.test( { bot1.setHtmlWithSelection( '[foo]' ); - evt = createDnDEventMock(); + evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor1 ); @@ -159,7 +159,7 @@ bender.test( { }, 'test setData getData': function() { - var evt = createDnDEventMock(), + var evt = createDragDropEventMock(), dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); dataTransfer.setData( 'Text', 'foo' ); @@ -169,8 +169,8 @@ bender.test( { }, 'test initDataTransfer binding': function() { - var evt1 = createDnDEventMock(), - evt2 = createDnDEventMock(), + var evt1 = createDragDropEventMock(), + evt2 = createDragDropEventMock(), dataTransferA = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ), dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ); @@ -191,7 +191,7 @@ bender.test( { bot.setHtmlWithSelection( '[foo]' ); - var evt = createDnDEventMock(), + var evt = createDragDropEventMock(), dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); dataTransfer.setTargetEditor( editor ); diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index bd1e6a445a8..9a668c827ca 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -5,7 +5,7 @@ CKEDITOR.disableAutoInline = true; -function createDnDEventMock() { +function createDragDropEventMock() { return { $: { clientX: 0, @@ -102,7 +102,7 @@ var editors, editorBots, testsForMultipleEditor = { 'test drop to header': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Header1

' + '

Lorem ipsum [dolor] sit amet.

' ); @@ -123,7 +123,7 @@ var editors, editorBots, 'test drop the same line, before': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum [dolor] sit amet.

' ); @@ -143,7 +143,7 @@ var editors, editorBots, 'test drop the same line, after': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); @@ -163,7 +163,7 @@ var editors, editorBots, 'test drop after paragraph': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); @@ -183,7 +183,7 @@ var editors, editorBots, 'test drop on the left from paragraph': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); @@ -203,7 +203,7 @@ var editors, editorBots, 'test drop from external source': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); @@ -226,7 +226,7 @@ var editors, editorBots, 'test drop html from external source': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); @@ -249,7 +249,7 @@ var editors, editorBots, 'test drop empty element from external source': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(); + evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); @@ -264,7 +264,7 @@ var editors, editorBots, 'test cross editor drop': function( editor ) { var bot = editorBots[ editor.name ], - evt = createDnDEventMock(), + evt = createDragDropEventMock(), botCross = editorBots[ 'cross' ], editorCross = botCross.editor; From 05e2a4a81695450a0ec743ed6756357f05285f28 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 16:16:17 +0200 Subject: [PATCH 40/67] Overwrite prototype instead of accessing it multiple times. --- plugins/clipboard/plugin.js | 153 ++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index c18095d7483..8bc10f4b05e 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1701,74 +1701,44 @@ } } } - }; - - /** - * Data transfer ID used to bind all dataTransfer - * object based on the same event (ex. in drag and drop events). - * - * @property {String} id - */ - - /** - * A native DOM event object. - * - * @private - * @property {Object} $ - */ - - /** - * Source editor, the editor where drag starts. - * Might be undefined if drag starts outside the editor (ex. dropping files to the editor). - * - * @property {CKEDITOR.editor} [sourceEditor] - */ - - /** - * Target editor, the editor where drop occurred. - * - * @property {CKEDITOR.editor} targetEditor - */ + /** + * Data transfer ID used to bind all dataTransfer + * object based on the same event (ex. in drag and drop events). + * + * @property {String} id + */ - /** - * HTML or text to be pasted. - * - * @property {String} dataValue - */ + /** + * A native DOM event object. + * + * @private + * @property {Object} $ + */ - /** - * Type of data in `data.dataValue`. The value might be `html` or `text`. - * - * @property {String} dataType - */ + /** + * Source editor, the editor where drag starts. + * Might be undefined if drag starts outside the editor (ex. dropping files to the editor). + * + * @property {CKEDITOR.editor} [sourceEditor] + */ - /** - * Facade for the native getData method. - * - * @param {String} type The type of data to retrieve. - * @returns {String} type Stored data for the given type or an empty string if data for that type does not exist. - */ - CKEDITOR.plugins.clipboard.dataTransfer.prototype.getData = function( type ) { - return this.$.getData( type ); - }; + /** + * Target editor, the editor where drop occurred. + * + * @property {CKEDITOR.editor} targetEditor + */ - /** - * Facade for the native setData method. - * - * @param {String} type The type of data to retrieve. - * @param {String} value The data to add. - */ - CKEDITOR.plugins.clipboard.dataTransfer.prototype.setData = function( type, value ) { - return this.$.setData( type, value ); - }; + /** + * HTML or text to be pasted. + * + * @property {String} dataValue + */ - /** - * Set target editor. - * - * @param {CKEDITOR.editor} editor The target editor instance. - */ - CKEDITOR.plugins.clipboard.dataTransfer.prototype.setTargetEditor = function( editor ) { - this.targetEditor = editor; + /** + * Type of data in `data.dataValue`. The value might be `html` or `text`. + * + * @property {String} dataType + */ }; /** @@ -1801,19 +1771,50 @@ */ CKEDITOR.DATA_TRANSFER_EXTERNAL = 2; - /** - * Get data transfer type. - * - * @returns {Number} type - * Possible options: DATA_TRANSFER_INTERNAL, DATA_TRANSFER_CROSS_EDITORS, DATA_TRANSFER_EXTERNAL. - */ - CKEDITOR.plugins.clipboard.dataTransfer.prototype.getTransferType = function() { - if ( !this.sourceEditor ) { - return CKEDITOR.DATA_TRANSFER_EXTERNAL; - } else if ( this.sourceEditor == this.targetEditor ) { - return CKEDITOR.DATA_TRANSFER_INTERNAL; - } else { - return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS; + CKEDITOR.plugins.clipboard.dataTransfer.prototype = { + /** + * Facade for the native getData method. + * + * @param {String} type The type of data to retrieve. + * @returns {String} type Stored data for the given type or an empty string if data for that type does not exist. + */ + getData: function( type ) { + return this.$.getData( type ); + }, + + /** + * Facade for the native setData method. + * + * @param {String} type The type of data to retrieve. + * @param {String} value The data to add. + */ + setData: function( type, value ) { + return this.$.setData( type, value ); + }, + + /** + * Set target editor. + * + * @param {CKEDITOR.editor} editor The target editor instance. + */ + setTargetEditor: function( editor ) { + this.targetEditor = editor; + }, + + /** + * Get data transfer type. + * + * @returns {Number} type + * Possible options: DATA_TRANSFER_INTERNAL, DATA_TRANSFER_CROSS_EDITORS, DATA_TRANSFER_EXTERNAL. + */ + getTransferType: function() { + if ( !this.sourceEditor ) { + return CKEDITOR.DATA_TRANSFER_EXTERNAL; + } else if ( this.sourceEditor == this.targetEditor ) { + return CKEDITOR.DATA_TRANSFER_INTERNAL; + } else { + return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS; + } } }; } )(); From 617714e757791f571ab00693f3e0e92d556ae4e9 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 17:14:11 +0200 Subject: [PATCH 41/67] Put dataTransfer in paste event. --- plugins/clipboard/plugin.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 8bc10f4b05e..f7889a1e530 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -611,7 +611,8 @@ dropRange = editor.createRange(); dropRange.moveToBookmark( dropBookmark ); dropRange.select(); - firePasteEvents( 'html', dataTransfer.dataValue ); + + firePasteWithDataTransfer( dataTransfer ); editor.fire( 'unlockSnapshot' ); }, 0 ); @@ -630,7 +631,8 @@ // Chrome have a problem with drop range (Chrome split the drop // range container so the offset is bigger then container length). dropRange.select(); - firePasteEvents( 'html', dataTransfer.dataValue ); + + firePasteWithDataTransfer( dataTransfer ); // Remove dragged content and make a snapshot. dataTransfer.sourceEditor.fire( 'saveSnapshot' ); @@ -651,7 +653,12 @@ // Paste content into the drop position. dropRange.select(); - firePasteEvents( dataTransfer.dataType, dataTransfer.dataValue ); + firePasteWithDataTransfer( dataTransfer ); + } + + function firePasteWithDataTransfer( dataTransfer ) { + if ( dataTransfer.dataValue ) + editor.fire( 'paste', dataTransfer ); } // Fix for Gecko bug with disappearing cursor. @@ -1694,10 +1701,12 @@ if ( !this.dataValue ) { // Try to get text data otherwise. this.dataValue = this.getData( 'Text' ); + this.dataType = 'text'; if ( this.dataValue ) { - CKEDITOR.tools.htmlEncode( this.getData( 'Text' ) ); - this.dataType = 'text'; + this.dataValue = CKEDITOR.tools.htmlEncode( this.dataValue ); + } else { + this.dataValue = ''; } } } From 3f516fa0dba7e588d327ce9d4315ba95b18e6e50 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Fri, 27 Jun 2014 17:24:26 +0200 Subject: [PATCH 42/67] Separated paste and drop. --- plugins/clipboard/plugin.js | 298 ++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 146 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index f7889a1e530..1ae0238b8d5 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -117,7 +117,8 @@ init: function( editor ) { var textificationFilter; - initClipboard( editor ); + initPasteClipboard( editor ); + initDragDrop( editor ); CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) ); @@ -262,7 +263,7 @@ } } ); - function initClipboard( editor ) { + function initPasteClipboard( editor ) { var preventBeforePasteEvent = 0, preventPasteEvent = 0, inReadOnly = 0, @@ -395,7 +396,7 @@ function addListeners() { editor.on( 'key', onKey ); - editor.on( 'contentDom', addListenersToEditable ); + editor.on( 'contentDom', addPasteListenersToEditable ); // For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that. editor.on( 'selectionChange', function( evt ) { @@ -417,7 +418,7 @@ } // Add events listeners to editable. - function addListenersToEditable() { + function addPasteListenersToEditable() { var editable = editor.editable(); // We'll be catching all pasted content in one line, regardless of whether @@ -524,148 +525,6 @@ } ); editable.on( 'keyup', setToolbarStates ); - - // DRAG & DROP - - // #11123 Firefox needs to listen on document, because otherwise event won't be fired. - // #11086 IE8 cannot listen on document. - var dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; - - // Listed on dragstart to mark internal and cross-editor drag & drop - // and save range and selected HTML. - editable.attachListener( dropTarget, 'dragstart', function( evt ) { - // Create a dataTransfer object and save it globally. - CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); - } ); - - // Clean up on dragend. - editable.attachListener( dropTarget, 'dragend', function( evt ) { - // When drag & drop is done we need to reset dataTransfer so the future - // external drop will be not recognize as internal. - CKEDITOR.plugins.clipboard.resetDataTransfer(); - } ); - - editable.attachListener( dropTarget, 'drop', function( evt ) { - // Cancel native drop. - evt.data.preventDefault(); - - // Create dataTransfer of get it, if it was created before. - var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt ); - dataTransfer.setTargetEditor( editor ); - - // Getting drop position is one of the most complex part. - var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), - dragRange = CKEDITOR.plugins.clipboard.dragRange; - - // Do nothing if it was not possible to get drop range. - if ( !dropRange ) - return; - - if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { - internalDrop( dragRange, dropRange, dataTransfer ); - } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { - crossEditorDrop( dragRange, dropRange, dataTransfer ); - } else { - externalDrop( dropRange, dataTransfer ); - } - } ); - } - - // Internal drag and drop (drag and drop in the same Editor). - function internalDrop( dragRange, dropRange, dataTransfer ) { - // Execute drop with a timeout because otherwise selection, after drop, - // on IE is in the drag position, instead of drop position. - setTimeout( function() { - var dragBookmark, dropBookmark, i, - rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore, - fixIESplittedNodes = CKEDITOR.plugins.clipboard.fixIESplittedNodes; - - // Save and lock snapshot so there will be only - // one snapshot for both remove and insert content. - editor.fire( 'saveSnapshot' ); - editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); - - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - fixIESplittedNodes( dragRange, dropRange ); - } - - // Because we manipulate multiple ranges we need to do it carefully, - // changing one range (event creating a bookmark) may make other invalid. - // We need to change ranges into bookmarks so we can manipulate them easily in the future. - // We can change the range which is later in the text before we change the preceding range. - // We call rangeBefore to test the order of ranges. - if ( !rangeBefore( dragRange, dropRange ) ) - dragBookmark = dragRange.createBookmark( 1 ); - - dropBookmark = dropRange.clone().createBookmark( 1 ); - - if ( rangeBefore( dragRange, dropRange ) ) - dragBookmark = dragRange.createBookmark( 1 ); - - // No we can safely delete content for the drag range... - dragRange = editor.createRange(); - dragRange.moveToBookmark( dragBookmark ); - dragRange.deleteContents(); - - // ...and paste content into the drop position. - dropRange = editor.createRange(); - dropRange.moveToBookmark( dropBookmark ); - dropRange.select(); - - firePasteWithDataTransfer( dataTransfer ); - - editor.fire( 'unlockSnapshot' ); - }, 0 ); - } - - // Cross editor drag and drop (drag in one Editor and drop in the other). - function crossEditorDrop( dragRange, dropRange, dataTransfer ) { - var i; - - // Because of FF bug we need to use this hack, otherwise cursor is hidden. - if ( CKEDITOR.env.gecko ) { - fixGeckoDisappearingCursor( editor ); - } - - // Paste event should be fired before delete contents because otherwise - // Chrome have a problem with drop range (Chrome split the drop - // range container so the offset is bigger then container length). - dropRange.select(); - - firePasteWithDataTransfer( dataTransfer ); - - // Remove dragged content and make a snapshot. - dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - - dragRange.deleteContents(); - - dataTransfer.sourceEditor.getSelection().reset(); - dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - } - - // Drop from external source. - function externalDrop( dropRange, dataTransfer ) { - // Because of FF bug we need to use this hack, otherwise cursor is hidden. - if ( CKEDITOR.env.gecko ) { - fixGeckoDisappearingCursor( editor ); - } - - // Paste content into the drop position. - dropRange.select(); - - firePasteWithDataTransfer( dataTransfer ); - } - - function firePasteWithDataTransfer( dataTransfer ) { - if ( dataTransfer.dataValue ) - editor.fire( 'paste', dataTransfer ); - } - - // Fix for Gecko bug with disappearing cursor. - function fixGeckoDisappearingCursor() { - editor.once( 'afterPaste', function() { - editor.toolbox.focus(); - } ); } // Create object representing Cut or Copy commands. @@ -1349,6 +1208,153 @@ return data; } + function initDragDrop( editor ) { + editor.on( 'contentDom', addDropListenersToEditable ); + + function addDropListenersToEditable() { + var editable = editor.editable(), + // #11123 Firefox needs to listen on document, because otherwise event won't be fired. + // #11086 IE8 cannot listen on document. + dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; + + // Listed on dragstart to mark internal and cross-editor drag & drop + // and save range and selected HTML. + editable.attachListener( dropTarget, 'dragstart', function( evt ) { + // Create a dataTransfer object and save it globally. + CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); + } ); + + // Clean up on dragend. + editable.attachListener( dropTarget, 'dragend', function( evt ) { + // When drag & drop is done we need to reset dataTransfer so the future + // external drop will be not recognize as internal. + CKEDITOR.plugins.clipboard.resetDataTransfer(); + } ); + + editable.attachListener( dropTarget, 'drop', function( evt ) { + // Cancel native drop. + evt.data.preventDefault(); + + // Create dataTransfer of get it, if it was created before. + var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt ); + dataTransfer.setTargetEditor( editor ); + + // Getting drop position is one of the most complex part. + var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), + dragRange = CKEDITOR.plugins.clipboard.dragRange; + + // Do nothing if it was not possible to get drop range. + if ( !dropRange ) + return; + + if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_INTERNAL ) { + internalDrop( dragRange, dropRange, dataTransfer ); + } else if ( dataTransfer.getTransferType() == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) { + crossEditorDrop( dragRange, dropRange, dataTransfer ); + } else { + externalDrop( dropRange, dataTransfer ); + } + } ); + + // Internal drag and drop (drag and drop in the same Editor). + function internalDrop( dragRange, dropRange, dataTransfer ) { + // Execute drop with a timeout because otherwise selection, after drop, + // on IE is in the drag position, instead of drop position. + setTimeout( function() { + var dragBookmark, dropBookmark, i, + rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore, + fixIESplittedNodes = CKEDITOR.plugins.clipboard.fixIESplittedNodes; + + // Save and lock snapshot so there will be only + // one snapshot for both remove and insert content. + editor.fire( 'saveSnapshot' ); + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { + fixIESplittedNodes( dragRange, dropRange ); + } + + // Because we manipulate multiple ranges we need to do it carefully, + // changing one range (event creating a bookmark) may make other invalid. + // We need to change ranges into bookmarks so we can manipulate them easily in the future. + // We can change the range which is later in the text before we change the preceding range. + // We call rangeBefore to test the order of ranges. + if ( !rangeBefore( dragRange, dropRange ) ) + dragBookmark = dragRange.createBookmark( 1 ); + + dropBookmark = dropRange.clone().createBookmark( 1 ); + + if ( rangeBefore( dragRange, dropRange ) ) + dragBookmark = dragRange.createBookmark( 1 ); + + // No we can safely delete content for the drag range... + dragRange = editor.createRange(); + dragRange.moveToBookmark( dragBookmark ); + dragRange.deleteContents(); + + // ...and paste content into the drop position. + dropRange = editor.createRange(); + dropRange.moveToBookmark( dropBookmark ); + dropRange.select(); + firePasteWithDataTransfer( dataTransfer ); + + editor.fire( 'unlockSnapshot' ); + }, 0 ); + } + + // Cross editor drag and drop (drag in one Editor and drop in the other). + function crossEditorDrop( dragRange, dropRange, dataTransfer ) { + var i; + + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); + } + + // Paste event should be fired before delete contents because otherwise + // Chrome have a problem with drop range (Chrome split the drop + // range container so the offset is bigger then container length). + dropRange.select(); + firePasteWithDataTransfer( dataTransfer ); + + // Remove dragged content and make a snapshot. + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); + + dragRange.deleteContents(); + + dataTransfer.sourceEditor.getSelection().reset(); + dataTransfer.sourceEditor.fire( 'saveSnapshot' ); + } + + // Drop from external source. + function externalDrop( dropRange, dataTransfer ) { + // Because of FF bug we need to use this hack, otherwise cursor is hidden. + if ( CKEDITOR.env.gecko ) { + fixGeckoDisappearingCursor( editor ); + } + + // Paste content into the drop position. + dropRange.select(); + + firePasteWithDataTransfer( dataTransfer ); + } + + function firePasteWithDataTransfer( dataTransfer ) { + if ( dataTransfer.dataValue ) { + editor.fire( 'paste', dataTransfer ); + } + } + + + // Fix for Gecko bug with disappearing cursor. + function fixGeckoDisappearingCursor() { + editor.once( 'afterPaste', function() { + editor.toolbox.focus(); + } ); + } + } + } + /** * @singleton * @class CKEDITOR.plugins.clipboard From 65d0094f6ff1279b4bb0659e958db69ba7810afa Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 30 Jun 2014 15:07:51 +0200 Subject: [PATCH 43/67] Tests: Fixed tests. --- plugins/clipboard/plugin.js | 1 + tests/plugins/clipboard/datatransfer.js | 9 ++++++--- tests/plugins/clipboard/drop.js | 16 +++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 1ae0238b8d5..c67e14db7ff 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1392,6 +1392,7 @@ */ CKEDITOR.plugins.clipboard.fixIESplittedNodes = function( dragRange, dropRange ) { if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && + dropRange.startOffset > 0 && dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 11ae4bb7b70..8d1ec1ef4a6 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -81,7 +81,8 @@ bender.test( { dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); - assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + // On Safari getSelectedHtml does not work properly. + assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); @@ -151,7 +152,8 @@ bender.test( { dataTransfer.setTargetEditor( editor2 ); assert.areSame( CKEDITOR.DATA_TRANSFER_CROSS_EDITORS, dataTransfer.getTransferType() ); - assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + // On Safari getSelectedHtml does not work properly. + assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor1, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor2, dataTransfer.targetEditor, 'targetEditor' ); @@ -196,7 +198,8 @@ bender.test( { dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); - assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + // On Safari getSelectedHtml does not work properly. + assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 9a668c827ca..1ba66382cc4 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -100,6 +100,10 @@ var editors, editorBots, } }, testsForMultipleEditor = { + 'setUp': function() { + CKEDITOR.plugins.clipboard.resetDataTransfer(); + }, + 'test drop to header': function( editor ) { var bot = editorBots[ editor.name ], evt = createDragDropEventMock(); @@ -117,7 +121,9 @@ var editors, editorBots, editor.execCommand( 'undo' ); - assert.areSame( '

Header1

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + wait( function() { + assert.areSame( '

Header1

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + }, 100 ); } ); }, @@ -298,7 +304,7 @@ var editors, editorBots, p, text; // Create DOM - bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + bot.setHtmlWithSelection( '

lorem ipsum sit amet.

' ); p = editor.document.getById( 'p' ); // Set drag range. @@ -306,7 +312,7 @@ var editors, editorBots, dragRange.collapse( true ); // Break content like IE do. - p.getChild( 0 ).setText( 'Lorem' ); + p.getChild( 0 ).setText( 'lorem' ); text = new CKEDITOR.dom.text( ' ipsum sit amet.' ); text.insertAfter( p.getChild( 0 ) ); @@ -320,9 +326,9 @@ var editors, editorBots, // Asserts. assert.areSame( 1, p.getChildCount() ); dragRange.select(); - assert.areSame( '

Lorem ipsum{} sit amet.

', bender.tools.selection.getWithHtml( editor ) ); + assert.isMatching( /lorem ipsum\{\} sit amet\.(
)?<\/p>/, bender.tools.selection.getWithHtml( editor ).toLowerCase() ); dropRange.select(); - assert.areSame( '

Lorem{} ipsum sit amet.

', bender.tools.selection.getWithHtml( editor ) ); + assert.isMatching( /lorem\{\} ipsum sit amet\.(
)?<\/p>/, bender.tools.selection.getWithHtml( editor ).toLowerCase() ); }, 'test rangeBefore 1': function() { From 67c4e768541c6eba36c5d0e49621cf097af06acf Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 30 Jun 2014 15:12:08 +0200 Subject: [PATCH 44/67] Tests: htmlEncode. --- tests/plugins/clipboard/datatransfer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 8d1ec1ef4a6..c6cb4d68b10 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -95,17 +95,17 @@ bender.test( { evt, dataTransfer; evt = createDragDropEventMock(); - evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + evt.data.$.dataTransfer.setData( 'Text', 'foo' ); dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); - assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( '<b>foo</b>', dataTransfer.dataValue, 'dataValue' ); assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); - assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); }, 'test dataTransfer external html': function() { From 0363c7ca87ddd922900fb7c5f7cc17f605cb1ec2 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 30 Jun 2014 15:33:55 +0200 Subject: [PATCH 45/67] All versions of IE needs focus. --- plugins/clipboard/plugin.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index c67e14db7ff..517ef40ac7a 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1455,6 +1455,11 @@ * */ CKEDITOR.plugins.clipboard.getRangeAtDropPosition = function( dropEvt, editor ) { + // If we drop content from the external source we need to call focus on IE. + if ( CKEDITOR.env.ie ) { + editor.focus(); + } + var $evt = dropEvt.data.$, x = $evt.clientX, y = $evt.clientY, @@ -1483,9 +1488,6 @@ return defaultRange; // IE 8. else if ( document.body.createTextRange ) { - // If we drop content from the different source we need to call focus on IE8. - editor.focus(); - $range = editor.document.getBody().$.createTextRange(); try { var sucess = false; From 35e5275bdfa14147820c523e8aca02f5f237c053 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Mon, 30 Jun 2014 17:28:24 +0200 Subject: [PATCH 46/67] Compare end (instead of start) of the first range with start of the second range in rangeBefore. --- plugins/clipboard/plugin.js | 8 ++++---- tests/plugins/clipboard/drop.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 517ef40ac7a..54182d674b8 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1426,8 +1426,8 @@ // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." // - if ( firstRange.startContainer.equals( secondRange.startContainer ) && - firstRange.startOffset < secondRange.startOffset ) + if ( firstRange.endContainer.equals( secondRange.startContainer ) && + firstRange.endOffset < secondRange.startOffset ) return true; // First range is inside a text node and the second is not, but if we change the @@ -1436,8 +1436,8 @@ // // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." // - if ( firstRange.startContainer.getParent().equals( secondRange.startContainer ) && - firstRange.startContainer.getIndex() < secondRange.startOffset ) + if ( firstRange.endContainer.getParent().equals( secondRange.startContainer ) && + firstRange.endContainer.getIndex() < secondRange.startOffset ) return true; return false; diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 1ba66382cc4..ea4871cc297 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -3,6 +3,9 @@ 'use strict'; +var setWithHtml = bender.tools.selection.setWithHtml, + getWithHtml = bender.tools.selection.getWithHtml; + CKEDITOR.disableAutoInline = true; function createDragDropEventMock() { @@ -167,6 +170,30 @@ var editors, editorBots, } ); }, + 'test drop after range end': function( editor ) { + var bot = editorBots[ editor.name ], + evt = createDragDropEventMock(); + + setWithHtml( editor, '

lor{em ipsum} dolor sit amet.

' ); + + drag( editor, evt ); + + drop( editor, evt, { + element: CKEDITOR.env.ie && CKEDITOR.env.version == 8 ? + editor.document.getById( 'p' ).getChild( 2 ) : + editor.document.getById( 'p' ).getChild( 1 ), + offset: CKEDITOR.env.ie && CKEDITOR.env.version == 8 ? + 11 : + 17 + }, function() { + assert.isMatching( /

lor<\/b> dolor sit em<\/b> ipsum(\[\]|\{\})amet\.(
)?<\/p>/, getWithHtml( editor ).toLowerCase(), 'after drop' ); + + editor.execCommand( 'undo' ); + + assert.isMatching( /

lorem<\/b> ipsum dolor sit \{\}amet\.(
)?<\/p>/, getWithHtml( editor ).toLowerCase(), 'after undo' ); + } ); + }, + 'test drop after paragraph': function( editor ) { var bot = editorBots[ editor.name ], evt = createDragDropEventMock(); From 3b68910d8cf5d861035e3036ac4834b852e45374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Wed, 2 Jul 2014 11:32:25 +0200 Subject: [PATCH 47/67] Clean up in drop tests. --- tests/plugins/clipboard/drop.js | 84 +++++++++++++++++---------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index ea4871cc297..7ec882bb196 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -4,7 +4,12 @@ 'use strict'; var setWithHtml = bender.tools.selection.setWithHtml, - getWithHtml = bender.tools.selection.getWithHtml; + getWithHtml = bender.tools.selection.getWithHtml, + htmlMatchOpts = { + compareSelection: true, + normalizeSelection: true, + fixStyles: true + }; CKEDITOR.disableAutoInline = true; @@ -41,7 +46,7 @@ function drop( editor, evt, config, callback ) { dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document, range = new CKEDITOR.dom.range( editor.document ), pasteEventCounter = 0, - expectedPasteEventCount = typeof config.expectedPasteEventCount !== 'undefined' ? + expectedPasteEventCount = typeof config.expectedPasteEventCount !== 'undefined' ? config.expectedPasteEventCount : 1; @@ -63,7 +68,7 @@ function drop( editor, evt, config, callback ) { assert.areSame( expectedPasteEventCount, pasteEventCounter, 'paste event should be called ' + expectedPasteEventCount + ' time(s)' ); callback(); - }, 100 ); + }, 10 ); } var editors, editorBots, @@ -72,16 +77,14 @@ var editors, editorBots, name: 'framed', creator: 'replace', config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + allowedContent: true } }, inline: { name: 'inline', creator: 'inline', config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + allowedContent: true } }, divarea: { @@ -89,16 +92,14 @@ var editors, editorBots, creator: 'replace', config: { extraPlugins: 'divarea', - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + allowedContent: true } }, cross: { name: 'cross', creator: 'replace', config: { - allowedContent: true, - toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] } ] + allowedContent: true } } }, @@ -117,16 +118,14 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { - element: editor.document.getById( 'h1' ).getChild( 0 ), + element: editor.document.getById( 'h1' ).getChild( 0 ), offset: 7 }, function() { assert.areSame( '

Header1dolor^

Lorem ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - wait( function() { - assert.areSame( '

Header1

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); - }, 100 ); + assert.areSame( '

Header1

Lorem ipsum dolor sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -139,14 +138,14 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum dolor sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -159,14 +158,14 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 2 ), + element: editor.document.getById( 'p' ).getChild( 2 ), offset: 11 }, function() { assert.areSame( '

Lorem dolor sit ipsum^amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum dolor sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -186,11 +185,11 @@ var editors, editorBots, 11 : 17 }, function() { - assert.isMatching( /

lor<\/b> dolor sit em<\/b> ipsum(\[\]|\{\})amet\.(
)?<\/p>/, getWithHtml( editor ).toLowerCase(), 'after drop' ); + assert.isInnerHtmlMatching( '

lor dolor sit em ipsum^amet.@

', getWithHtml( editor ), htmlMatchOpts, 'after drop' ); editor.execCommand( 'undo' ); - assert.isMatching( /

lorem<\/b> ipsum dolor sit \{\}amet\.(
)?<\/p>/, getWithHtml( editor ).toLowerCase(), 'after undo' ); + assert.isInnerHtmlMatching( '

lorem ipsum dolor sit ^amet.@

', getWithHtml( editor ), htmlMatchOpts, 'after undo' ); } ); }, @@ -203,14 +202,14 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 2 ), + element: editor.document.getById( 'p' ).getChild( 2 ), offset: 16 }, function() { assert.areSame( '

Lorem dolor sit amet.ipsum^

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum dolor sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -223,14 +222,14 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 0 }, function() { - assert.isMatching( /

ipsum\^Lorem dolor sit amet\.<\/p>/, bender.tools.compatHtml( bender.tools.getHtmlWithSelection( editor ), 0, 1, 0, 1 ), 'after drop' ); + assert.isInnerHtmlMatching( '

ipsum^Lorem dolor sit amet.

', getWithHtml( editor ), htmlMatchOpts, 'after drop' ); editor.execCommand( 'undo' ); - assert.isMatching( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editor.getData(), 1, 1, 1, 1 ), 'after undo' ); + assert.isInnerHtmlMatching( '

Lorem ipsum dolor sit amet.

', editor.getData(), htmlMatchOpts, 'after undo' ); } ); }, @@ -246,14 +245,14 @@ var editors, editorBots, evt.$.dataTransfer.setData( 'text/html', 'dolor' ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -269,14 +268,14 @@ var editors, editorBots, evt.$.dataTransfer.setData( 'text/html', 'dolor' ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { assert.areSame( '

Lorem dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); editor.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); + assert.areSame( '

Lorem ipsum sit amet.

', editor.getData(), 'after undo' ); } ); }, @@ -287,7 +286,7 @@ var editors, editorBots, bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6, expectedPasteEventCount: 0 }, function() { @@ -301,24 +300,23 @@ var editors, editorBots, botCross = editorBots[ 'cross' ], editorCross = botCross.editor; - bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); - botCross.setHtmlWithSelection( '

Lorem [ipsum dolor] sit amet.

' ); + setWithHtml( bot.editor, '

Lorem ipsum sit amet.

' ); + setWithHtml( botCross.editor, '

Lorem {ipsum dolor }sit amet.

' ); drag( editorCross, evt ); drop( editor, evt, { - element: editor.document.getById( 'p' ).getChild( 0 ), + element: editor.document.getById( 'p' ).getChild( 0 ), offset: 6 }, function() { - assert.areSame( '

Lorem ipsum dolor^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); - - assert.areSame( '

Lorem sit amet.

', bender.tools.compatHtml( editorCross.getData(), 0, 1, 0, 1 ), 'after drop - editor cross' ); + assert.areSame( '

Lorem ipsum dolor ^ipsum sit amet.

', bender.tools.getHtmlWithSelection( editor ), 'after drop' ); + assert.areSame( '

Lorem sit amet.

', editorCross.getData(), 'after drop - editor cross' ); editor.execCommand( 'undo' ); editorCross.execCommand( 'undo' ); - assert.areSame( '

Lorem ipsum sit amet.

', bender.tools.compatHtml( editor.getData(), 0, 1, 0, 1 ), 'after undo' ); - assert.areSame( '

Lorem ipsum dolor sit amet.

', bender.tools.compatHtml( editorCross.getData(), 0, 1, 0, 1 ), 'after undo - editor cross' ); + assert.areSame( '

Lorem ipsum sit amet.

', editor.getData(), 'after undo' ); + assert.areSame( '

Lorem ipsum dolor sit amet.

', editorCross.getData(), 'after undo - editor cross' ); } ); } }, @@ -353,9 +351,9 @@ var editors, editorBots, // Asserts. assert.areSame( 1, p.getChildCount() ); dragRange.select(); - assert.isMatching( /lorem ipsum\{\} sit amet\.(
)?<\/p>/, bender.tools.selection.getWithHtml( editor ).toLowerCase() ); + assert.isInnerHtmlMatching( '

lorem ipsum^ sit amet\.@

', getWithHtml( editor ), htmlMatchOpts ); dropRange.select(); - assert.isMatching( /lorem\{\} ipsum sit amet\.(
)?<\/p>/, bender.tools.selection.getWithHtml( editor ).toLowerCase() ); + assert.isInnerHtmlMatching( '

lorem^ ipsum sit amet.@

', getWithHtml( editor ), htmlMatchOpts ); }, 'test rangeBefore 1': function() { @@ -429,6 +427,10 @@ bender.tools.setUpEditors( editorsDefinitions, function( e, eb ) { editors = e; editorBots = eb; + for ( var name in editors ) { + editors[ name ].dataProcessor.writer.sortAttributes = true; + } + bender.test( CKEDITOR.tools.extend( bender.tools.createTestsForEditors( [ editors.framed, editors.inline, editors.divarea ], From 93f0066112ddb68fd68260b8d1092511698c8871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Wed, 2 Jul 2014 13:21:52 +0200 Subject: [PATCH 48/67] Undo tests must be run precisely - reset stack and then wait for afterPaste - avoid guessing timeouts. --- tests/plugins/clipboard/drop.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 7ec882bb196..caf7765e11c 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -62,13 +62,22 @@ function drop( editor, evt, config, callback ) { pasteEventCounter++; } ); - dropTarget.fire( 'drop', evt ); + if ( expectedPasteEventCount ) { + editor.once( 'afterPaste', function() { + resume( finish ); + } ); + // Ensure async. + wait( function() { + dropTarget.fire( 'drop', evt ); + } ); + } else { + wait( finish, 100 ); + } - wait( function() { + function finish() { assert.areSame( expectedPasteEventCount, pasteEventCounter, 'paste event should be called ' + expectedPasteEventCount + ' time(s)' ); - callback(); - }, 10 ); + } } var editors, editorBots, @@ -114,6 +123,7 @@ var editors, editorBots, bot.setHtmlWithSelection( '

Header1

' + '

Lorem ipsum [dolor] sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -134,6 +144,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum [dolor] sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -154,6 +165,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -174,6 +186,7 @@ var editors, editorBots, evt = createDragDropEventMock(); setWithHtml( editor, '

lor{em ipsum} dolor sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -198,6 +211,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -218,6 +232,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem [ipsum] dolor sit amet.

' ); + editor.resetUndo(); drag( editor, evt ); @@ -238,6 +253,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + editor.resetUndo(); if ( CKEDITOR.env.ie ) evt.$.dataTransfer.setData( 'Text', 'dolor' ); @@ -261,6 +277,7 @@ var editors, editorBots, evt = createDragDropEventMock(); bot.setHtmlWithSelection( '

Lorem ipsum sit amet.

' ); + editor.resetUndo(); if ( CKEDITOR.env.ie ) evt.$.dataTransfer.setData( 'Text', 'dolor' ); @@ -283,6 +300,7 @@ var editors, editorBots, var bot = editorBots[ editor.name ], evt = createDragDropEventMock(); + editor.resetUndo(); bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); drop( editor, evt, { @@ -302,6 +320,8 @@ var editors, editorBots, setWithHtml( bot.editor, '

Lorem ipsum sit amet.

' ); setWithHtml( botCross.editor, '

Lorem {ipsum dolor }sit amet.

' ); + bot.editor.resetUndo(); + botCross.editor.resetUndo(); drag( editorCross, evt ); From 08534e7c3837bb4b3fa86478ab6577467b5f38f8 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 2 Jul 2014 15:03:50 +0200 Subject: [PATCH 49/67] Call drop even if expectedPasteEventCount=0. --- tests/plugins/clipboard/drop.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index caf7765e11c..9cb1d42f149 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -66,14 +66,15 @@ function drop( editor, evt, config, callback ) { editor.once( 'afterPaste', function() { resume( finish ); } ); - // Ensure async. - wait( function() { - dropTarget.fire( 'drop', evt ); - } ); } else { wait( finish, 100 ); } + // Ensure async. + wait( function() { + dropTarget.fire( 'drop', evt ); + } ); + function finish() { assert.areSame( expectedPasteEventCount, pasteEventCounter, 'paste event should be called ' + expectedPasteEventCount + ' time(s)' ); callback(); From 9c6122af52aebfd18e00097c1705c4efe2d37c7a Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 2 Jul 2014 15:23:50 +0200 Subject: [PATCH 50/67] Tests: Call resetDataTransfer in the setUp. --- tests/plugins/clipboard/datatransfer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index c6cb4d68b10..2e33e6cbf8f 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -53,6 +53,10 @@ bender.test( { } ); }, + setUp: function() { + CKEDITOR.plugins.clipboard.resetDataTransfer(); + }, + 'test dataTransfer id': function() { var evt1 = createDragDropEventMock(), evt2 = createDragDropEventMock(), @@ -183,8 +187,6 @@ bender.test( { dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt2 ); assert.areNotSame( dataTransferA, dataTransferB ); - - CKEDITOR.plugins.clipboard.resetDataTransfer(); }, 'test initDataTransfer constructor': function() { @@ -203,7 +205,5 @@ bender.test( { assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); - - CKEDITOR.plugins.clipboard.resetDataTransfer(); } } ); \ No newline at end of file From 1887bc900400e30219ae8c3143e8c48f8580e36e Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 2 Jul 2014 15:28:11 +0200 Subject: [PATCH 51/67] Tests: Used 'xfoox' instead of 'foo'. --- tests/plugins/clipboard/datatransfer.js | 27 +++++++++++-------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 2e33e6cbf8f..ea74f31e89c 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -76,7 +76,7 @@ bender.test( { editor = this.editors.editor1, evt, dataTransfer; - bot.setHtmlWithSelection( '[foo]' ); + bot.setHtmlWithSelection( '[xfoox]' ); evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); @@ -85,8 +85,7 @@ bender.test( { dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); - // On Safari getSelectedHtml does not work properly. - assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); + assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); @@ -99,17 +98,17 @@ bender.test( { evt, dataTransfer; evt = createDragDropEventMock(); - evt.data.$.dataTransfer.setData( 'Text', 'foo' ); + evt.data.$.dataTransfer.setData( 'Text', 'xfoox' ); dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); - assert.areSame( '<b>foo</b>', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'x<b>foo</b>x', dataTransfer.dataValue, 'dataValue' ); assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); - assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); + assert.areSame( 'xfoox', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); }, 'test dataTransfer external html': function() { @@ -119,7 +118,7 @@ bender.test( { evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); if ( !CKEDITOR.env.ie ) { - evt.data.$.dataTransfer.setData( 'text/html', 'foo' ); + evt.data.$.dataTransfer.setData( 'text/html', 'xfoox' ); } dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); @@ -134,10 +133,10 @@ bender.test( { assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); } else { - assert.areSame( 'foo', dataTransfer.dataValue, 'dataValue' ); + assert.areSame( 'xfoox', dataTransfer.dataValue, 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); - assert.areSame( 'foo', dataTransfer.getData( 'text/html' ), 'getData( \'text/html\' )' ); + assert.areSame( 'xfoox', dataTransfer.getData( 'text/html' ), 'getData( \'text/html\' )' ); } }, @@ -147,7 +146,7 @@ bender.test( { editor2 = this.editors.editor2, evt, dataTransfer; - bot1.setHtmlWithSelection( '[foo]' ); + bot1.setHtmlWithSelection( '[xfoox]' ); evt = createDragDropEventMock(); evt.data.$.dataTransfer.setData( 'Text', 'foo' ); @@ -156,8 +155,7 @@ bender.test( { dataTransfer.setTargetEditor( editor2 ); assert.areSame( CKEDITOR.DATA_TRANSFER_CROSS_EDITORS, dataTransfer.getTransferType() ); - // On Safari getSelectedHtml does not work properly. - assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); + assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor1, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor2, dataTransfer.targetEditor, 'targetEditor' ); @@ -193,15 +191,14 @@ bender.test( { var bot = this.bots.editor1, editor = this.editors.editor1; - bot.setHtmlWithSelection( '[foo]' ); + bot.setHtmlWithSelection( '[xfoox]' ); var evt = createDragDropEventMock(), dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); - // On Safari getSelectedHtml does not work properly. - assert.areSame( CKEDITOR.env.safari ? 'foo' : 'foo', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); + assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); From a189f5c94eb53e0fb0965e0065db1f9a5b7dcbb4 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Wed, 2 Jul 2014 16:56:24 +0200 Subject: [PATCH 52/67] Fixed tests on FF and IE10. --- tests/plugins/clipboard/drop.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 9cb1d42f149..a77194481a1 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -67,7 +67,7 @@ function drop( editor, evt, config, callback ) { resume( finish ); } ); } else { - wait( finish, 100 ); + wait( finish, 300 ); } // Ensure async. @@ -241,11 +241,11 @@ var editors, editorBots, element: editor.document.getById( 'p' ).getChild( 0 ), offset: 0 }, function() { - assert.isInnerHtmlMatching( '

ipsum^Lorem dolor sit amet.

', getWithHtml( editor ), htmlMatchOpts, 'after drop' ); + assert.isInnerHtmlMatching( '

ipsum^Lorem dolor sit amet.@

', getWithHtml( editor ), htmlMatchOpts, 'after drop' ); editor.execCommand( 'undo' ); - assert.isInnerHtmlMatching( '

Lorem ipsum dolor sit amet.

', editor.getData(), htmlMatchOpts, 'after undo' ); + assert.isInnerHtmlMatching( '

Lorem ipsum dolor sit amet.@

', editor.getData(), htmlMatchOpts, 'after undo' ); } ); }, From 6ecc615cb0c805837bbb2931002ba84c7a4a7c20 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 11:15:06 +0200 Subject: [PATCH 53/67] Tests: Added docs to the 'test drop after range end'. --- tests/plugins/clipboard/drop.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index a77194481a1..c18dc70037b 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -192,6 +192,7 @@ var editors, editorBots, drag( editor, evt ); drop( editor, evt, { + // IE8 split text node anyway so we need different drop position there. element: CKEDITOR.env.ie && CKEDITOR.env.version == 8 ? editor.document.getById( 'p' ).getChild( 2 ) : editor.document.getById( 'p' ).getChild( 1 ), From 13a069f5fcbaae10b1bcb1e50ea32262bc4b62d5 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 11:29:57 +0200 Subject: [PATCH 54/67] Tests: Improved datatransfer tests descriptions. --- tests/plugins/clipboard/datatransfer.js | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index ea74f31e89c..04e92b1dcb2 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -57,21 +57,21 @@ bender.test( { CKEDITOR.plugins.clipboard.resetDataTransfer(); }, - 'test dataTransfer id': function() { + 'test id': function() { var evt1 = createDragDropEventMock(), evt2 = createDragDropEventMock(), dataTransfer1a = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), dataTransfer1b = new CKEDITOR.plugins.clipboard.dataTransfer( evt1 ), dataTransfer2 = new CKEDITOR.plugins.clipboard.dataTransfer( evt2 ); - assert.areSame( dataTransfer1a.id, dataTransfer1b.id ); + assert.areSame( dataTransfer1a.id, dataTransfer1b.id, 'Ids for object based on the same event should be the same.' ); // In IE10+ we can not use any data type besides text, so id is fixed. if ( !CKEDITOR.env.ie || CKEDITOR.env.version < 10 ) - assert.areNotSame( dataTransfer1a.id, dataTransfer2.id ); + assert.areNotSame( dataTransfer1a.id, dataTransfer2.id, 'Ids for object based on different events should be different.' ); }, - 'test dataTransfer internal': function() { + 'test internal drag drop': function() { var bot = this.bots.editor1, editor = this.editors.editor1, evt, dataTransfer; @@ -84,7 +84,7 @@ bender.test( { dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor ); dataTransfer.setTargetEditor( editor ); - assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); + assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType(), 'transferType' ); assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); @@ -93,7 +93,7 @@ bender.test( { }, - 'test dataTransfer external text': function() { + 'test drop text from external source': function() { var editor = this.editors.editor1, evt, dataTransfer; @@ -103,7 +103,7 @@ bender.test( { dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); dataTransfer.setTargetEditor( editor ); - assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); + assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType(), 'transferType' ); assert.areSame( 'x<b>foo</b>x', dataTransfer.dataValue, 'dataValue' ); assert.areSame( 'text', dataTransfer.dataType, 'dataType' ); assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); @@ -111,7 +111,7 @@ bender.test( { assert.areSame( 'xfoox', dataTransfer.getData( 'Text' ), 'getData( \'Text\' )' ); }, - 'test dataTransfer external html': function() { + 'test drop html from external source': function() { var editor = this.editors.editor1, evt, dataTransfer; @@ -124,7 +124,7 @@ bender.test( { dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt ); dataTransfer.setTargetEditor( editor ); - assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType() ); + assert.areSame( CKEDITOR.DATA_TRANSFER_EXTERNAL, dataTransfer.getTransferType(), 'transferType' ); assert.isUndefined( dataTransfer.sourceEditor, 'sourceEditor' ); assert.areSame( editor, dataTransfer.targetEditor, 'targetEditor' ); @@ -140,7 +140,7 @@ bender.test( { } }, - 'test dataTransfer cross': function() { + 'test drag drop between editors': function() { var bot1 = this.bots.editor1, editor1 = this.editors.editor1, editor2 = this.editors.editor2, @@ -154,7 +154,7 @@ bender.test( { dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, editor1 ); dataTransfer.setTargetEditor( editor2 ); - assert.areSame( CKEDITOR.DATA_TRANSFER_CROSS_EDITORS, dataTransfer.getTransferType() ); + assert.areSame( CKEDITOR.DATA_TRANSFER_CROSS_EDITORS, dataTransfer.getTransferType(), 'transferType' ); assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor1, dataTransfer.sourceEditor, 'sourceEditor' ); @@ -168,7 +168,7 @@ bender.test( { dataTransfer.setData( 'Text', 'foo' ); - assert.areSame( 'foo', dataTransfer.getData( 'Text' ) ); + assert.areSame( 'foo', dataTransfer.getData( 'Text' ), 'data should match set data' ); }, @@ -178,13 +178,13 @@ bender.test( { dataTransferA = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ), dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ); - assert.areSame( dataTransferA, dataTransferB ); + assert.areSame( dataTransferA, dataTransferB, 'If we init dataTransfer object twice on the same event this should be the same object.' ); CKEDITOR.plugins.clipboard.resetDataTransfer(); dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt2 ); - assert.areNotSame( dataTransferA, dataTransferB ); + assert.areNotSame( dataTransferA, dataTransferB, 'If we init dataTransfer object twice on different events these should be different objects.' ); }, 'test initDataTransfer constructor': function() { @@ -197,7 +197,7 @@ bender.test( { dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); dataTransfer.setTargetEditor( editor ); - assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType() ); + assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType(), 'transferType' ); assert.areSame( 'xfoox', bender.tools.fixHtml( dataTransfer.dataValue ), 'dataValue' ); assert.areSame( 'html', dataTransfer.dataType, 'dataType' ); assert.areSame( editor, dataTransfer.sourceEditor, 'sourceEditor' ); From 228d7e852d3c47ac429b676c513b5e31e7d7c62b Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 11:39:15 +0200 Subject: [PATCH 55/67] Made addDropListenersToEditable anonymous function. --- plugins/clipboard/plugin.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 54182d674b8..a3e15d91f28 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1209,9 +1209,7 @@ } function initDragDrop( editor ) { - editor.on( 'contentDom', addDropListenersToEditable ); - - function addDropListenersToEditable() { + editor.on( 'contentDom', function() { var editable = editor.editable(), // #11123 Firefox needs to listen on document, because otherwise event won't be fired. // #11086 IE8 cannot listen on document. @@ -1352,7 +1350,7 @@ editor.toolbox.focus(); } ); } - } + } ); } /** From 2e7311e2f38ab2f2554f94fbf2b5019a2d1c8831 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 11:42:51 +0200 Subject: [PATCH 56/67] Do not cache fixIESplittedNodes and rangeBefore functions. Cache rangeBefore result. --- plugins/clipboard/plugin.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index a3e15d91f28..7ac962902ad 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1259,9 +1259,7 @@ // Execute drop with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { - var dragBookmark, dropBookmark, i, - rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore, - fixIESplittedNodes = CKEDITOR.plugins.clipboard.fixIESplittedNodes; + var dragBookmark, dropBookmark, i, rangeBefore; // Save and lock snapshot so there will be only // one snapshot for both remove and insert content. @@ -1269,7 +1267,7 @@ editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - fixIESplittedNodes( dragRange, dropRange ); + CKEDITOR.plugins.clipboard.fixIESplittedNodes( dragRange, dropRange ); } // Because we manipulate multiple ranges we need to do it carefully, @@ -1277,13 +1275,17 @@ // We need to change ranges into bookmarks so we can manipulate them easily in the future. // We can change the range which is later in the text before we change the preceding range. // We call rangeBefore to test the order of ranges. - if ( !rangeBefore( dragRange, dropRange ) ) + rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore( dragRange, dropRange ); + + if ( !rangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); + } dropBookmark = dropRange.clone().createBookmark( 1 ); - if ( rangeBefore( dragRange, dropRange ) ) + if ( rangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); + } // No we can safely delete content for the drag range... dragRange = editor.createRange(); From 35968525b5ac71af9e6a31bc1c0b8e16fe8cc535 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 13:48:13 +0200 Subject: [PATCH 57/67] Replaced CKEDITOR.plugins.clipboard with clipboard or this. --- plugins/clipboard/plugin.js | 517 ++++++++++++++++++------------------ 1 file changed, 258 insertions(+), 259 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 7ac962902ad..cf11cdc9459 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1209,6 +1209,8 @@ } function initDragDrop( editor ) { + var clipboard = CKEDITOR.plugins.clipboard; + editor.on( 'contentDom', function() { var editable = editor.editable(), // #11123 Firefox needs to listen on document, because otherwise event won't be fired. @@ -1219,14 +1221,14 @@ // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it globally. - CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); + clipboard.initDataTransfer( evt, editor ); } ); // Clean up on dragend. editable.attachListener( dropTarget, 'dragend', function( evt ) { // When drag & drop is done we need to reset dataTransfer so the future // external drop will be not recognize as internal. - CKEDITOR.plugins.clipboard.resetDataTransfer(); + clipboard.resetDataTransfer(); } ); editable.attachListener( dropTarget, 'drop', function( evt ) { @@ -1234,12 +1236,12 @@ evt.data.preventDefault(); // Create dataTransfer of get it, if it was created before. - var dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt ); + var dataTransfer = clipboard.initDataTransfer( evt ); dataTransfer.setTargetEditor( editor ); // Getting drop position is one of the most complex part. - var dropRange = CKEDITOR.plugins.clipboard.getRangeAtDropPosition( evt, editor ), - dragRange = CKEDITOR.plugins.clipboard.dragRange; + var dropRange = clipboard.getRangeAtDropPosition( evt, editor ), + dragRange = clipboard.dragRange; // Do nothing if it was not possible to get drop range. if ( !dropRange ) @@ -1267,7 +1269,7 @@ editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - CKEDITOR.plugins.clipboard.fixIESplittedNodes( dragRange, dropRange ); + clipboard.fixIESplittedNodes( dragRange, dropRange ); } // Because we manipulate multiple ranges we need to do it carefully, @@ -1275,14 +1277,11 @@ // We need to change ranges into bookmarks so we can manipulate them easily in the future. // We can change the range which is later in the text before we change the preceding range. // We call rangeBefore to test the order of ranges. - rangeBefore = CKEDITOR.plugins.clipboard.rangeBefore( dragRange, dropRange ); - + rangeBefore = clipboard.rangeBefore( dragRange, dropRange ); if ( !rangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); } - dropBookmark = dropRange.clone().createBookmark( 1 ); - if ( rangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); } @@ -1359,280 +1358,280 @@ * @singleton * @class CKEDITOR.plugins.clipboard */ - CKEDITOR.plugins.clipboard = {}; + CKEDITOR.plugins.clipboard = { + /** + * IE 8 & 9 split text node on drop so the first node contains + * text before drop position and the second contains rest. If we + * drag the content from the same node we will be not able to get + * it (range became invalid), so we need to join them back. + * + * Notify that first node on IE 8 & 9 is the original node object + * but with shortened content. + * + * Before: + * --- Text Node A ---------------------------------- + * /\ + * Drag position + * + * After (IE 8 & 9): + * --- Text Node A ----- --- Text Node B ----------- + * /\ /\ + * Drop position Drag position + * (invalid) + * + * After (other browsers): + * --- Text Node A ---------------------------------- + * /\ /\ + * Drop position Drag position + * + * This function is in the public scope for tests usage only. + * + * @private + */ + fixIESplittedNodes: function( dragRange, dropRange ) { + if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && + dropRange.startOffset > 0 && + dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && + dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { + var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), + nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), + offset = nodeBefore.getLength(); + + if ( nodeAfter ) { + nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); + nodeAfter.remove(); + } - /** - * @private - * - * IE 8 & 9 split text node on drop so the first node contains - * text before drop position and the second contains rest. If we - * drag the content from the same node we will be not able to get - * it (range became invalid), so we need to join them back. - * - * Notify that first node on IE 8 & 9 is the original node object - * but with shortened content. - * - * Before: - * --- Text Node A ---------------------------------- - * /\ - * Drag position - * - * After (IE 8 & 9): - * --- Text Node A ----- --- Text Node B ----------- - * /\ /\ - * Drop position Drag position - * (invalid) - * - * After (other browsers): - * --- Text Node A ---------------------------------- - * /\ /\ - * Drop position Drag position - * - * This function is in the public scope for tests usage only. - */ - CKEDITOR.plugins.clipboard.fixIESplittedNodes = function( dragRange, dropRange ) { - if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && - dropRange.startOffset > 0 && - dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && - dropRange.startContainer.getChild( dropRange.startOffset - 1 ).equals( dragRange.startContainer ) ) { - var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ), - nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset ), - offset = nodeBefore.getLength(); - - if ( nodeAfter ) { - nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() ); - nodeAfter.remove(); + dropRange.setStart( nodeBefore, offset ); + dropRange.collapse( true ); } + }, - dropRange.setStart( nodeBefore, offset ); - dropRange.collapse( true ); - } - }; - - /** - * @private - * - * Check if the beginning of the firstRange is before the beginning of the secondRange - * and modification of the content in the firstRange may break secondRange. - * - * Notify that this function returns false if these two ranges are in two - * separate nodes and do not affect each other (even if firstRange is before secondRange). - * - * This function is in the public scope for tests usage only. - */ - CKEDITOR.plugins.clipboard.rangeBefore = function( firstRange, secondRange ) { - // Both ranges has the same parent and the first has smaller offset. E.g.: - // - // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." - // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." - // - if ( firstRange.endContainer.equals( secondRange.startContainer ) && - firstRange.endOffset < secondRange.startOffset ) - return true; - - // First range is inside a text node and the second is not, but if we change the - // first range into bookmark and split the text node then the seconds node offset - // will be no longer correct. - // - // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." - // - if ( firstRange.endContainer.getParent().equals( secondRange.startContainer ) && - firstRange.endContainer.getIndex() < secondRange.startOffset ) - return true; - - return false; - }; + /** + * Check if the beginning of the firstRange is before the beginning of the secondRange + * and modification of the content in the firstRange may break secondRange. + * + * Notify that this function returns false if these two ranges are in two + * separate nodes and do not affect each other (even if firstRange is before secondRange). + * + * This function is in the public scope for tests usage only. + * + * @private + */ + rangeBefore: function( firstRange, secondRange ) { + // Both ranges has the same parent and the first has smaller offset. E.g.: + // + // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." + // "Lorem ipsum dolor sit" [1] "amet consectetur" [2] "adipiscing elit." + // + if ( firstRange.endContainer.equals( secondRange.startContainer ) && + firstRange.endOffset < secondRange.startOffset ) + return true; - /** - * Get range from the drop event. - * - * Copy of getRangeAtDropPosition method from widget plugin. - * In #11219 method in widget should be removed and everything be according to DRY. - * - * @param {Object} domEvent A native DOM drop event object. - * @param {CKEDITOR.editor} editor The source editor instance. - * @returns {CKEDITOR.dom.range} range at drop position. - * - */ - CKEDITOR.plugins.clipboard.getRangeAtDropPosition = function( dropEvt, editor ) { - // If we drop content from the external source we need to call focus on IE. - if ( CKEDITOR.env.ie ) { - editor.focus(); - } + // First range is inside a text node and the second is not, but if we change the + // first range into bookmark and split the text node then the seconds node offset + // will be no longer correct. + // + // "Lorem ipsum dolor sit [1] amet" "consectetur" [2] "adipiscing elit." + // + if ( firstRange.endContainer.getParent().equals( secondRange.startContainer ) && + firstRange.endContainer.getIndex() < secondRange.startOffset ) + return true; - var $evt = dropEvt.data.$, - x = $evt.clientX, - y = $evt.clientY, - $range, - defaultRange = editor.getSelection( true ).getRanges()[ 0 ], - range = editor.createRange(); - - // Make testing possible. - if ( dropEvt.data.testRange ) - return dropEvt.data.testRange; - - // Webkits. - if ( document.caretRangeFromPoint ) { - $range = editor.document.$.caretRangeFromPoint( x, y ); - range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); - range.collapse( true ); - } - // FF. - else if ( $evt.rangeParent ) { - range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); - range.collapse( true ); - } - // IEs 9+. - else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 ) - // On IE 9+ range by default is where we expected it. - return defaultRange; - // IE 8. - else if ( document.body.createTextRange ) { - $range = editor.document.getBody().$.createTextRange(); - try { - var sucess = false; + return false; + }, - // If user drop between text line IEs moveToPoint throws exception: - // - // Lorem ipsum pulvinar purus et euismod - // - // dolor sit amet,| consectetur adipiscing - // * - // vestibulum tincidunt augue eget tempus. - // - // * - drop position - // | - expected cursor position - // - // So we try to call moveToPoint with +-1px up to +-20px above or - // below original drop position to find nearest good drop position. - for ( var i = 0; i < 20 && !sucess; i++ ) { - if ( !sucess ) { - try { - $range.moveToPoint( x, y - i ); - sucess = true; - } catch ( err ) { - } - } - if ( !sucess ) { - try { - $range.moveToPoint( x, y + i ); - sucess = true; - } catch ( err ) { - } - } - } + /** + * Get range from the drop event. + * + * Copy of getRangeAtDropPosition method from widget plugin. + * In #11219 method in widget should be removed and everything be according to DRY. + * + * @param {Object} domEvent A native DOM drop event object. + * @param {CKEDITOR.editor} editor The source editor instance. + * @returns {CKEDITOR.dom.range} range at drop position. + * + */ + getRangeAtDropPosition: function( dropEvt, editor ) { + // If we drop content from the external source we need to call focus on IE. + if ( CKEDITOR.env.ie ) { + editor.focus(); + } - if ( sucess ) { - var id = 'cke-temp-' + ( new Date() ).getTime(); - $range.pasteHTML( '\u200b' ); + var $evt = dropEvt.data.$, + x = $evt.clientX, + y = $evt.clientY, + $range, + defaultRange = editor.getSelection( true ).getRanges()[ 0 ], + range = editor.createRange(); + + // Make testing possible. + if ( dropEvt.data.testRange ) + return dropEvt.data.testRange; + + // Webkits. + if ( document.caretRangeFromPoint ) { + $range = editor.document.$.caretRangeFromPoint( x, y ); + range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); + range.collapse( true ); + } + // FF. + else if ( $evt.rangeParent ) { + range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); + range.collapse( true ); + } + // IEs 9+. + else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 ) + // On IE 9+ range by default is where we expected it. + return defaultRange; + // IE 8. + else if ( document.body.createTextRange ) { + $range = editor.document.getBody().$.createTextRange(); + try { + var sucess = false; - var span = editor.document.getById( id ); - range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); - span.remove(); - } else { - // If the fist method does not succeed we might be next to - // the short element (like header): - // - // Lorem ipsum pulvinar purus et euismod. - // - // - // SOME HEADER| * + // If user drop between text line IEs moveToPoint throws exception: // + // Lorem ipsum pulvinar purus et euismod // + // dolor sit amet,| consectetur adipiscing + // * // vestibulum tincidunt augue eget tempus. // // * - drop position // | - expected cursor position // - // In such situation elementFromPoint returns proper element. Using getClientRect - // it is possible to check if the cursor should be at the beginning or at the end - // of paragraph. - var $element = editor.document.$.elementFromPoint( x, y ), - element = new CKEDITOR.dom.element( $element ), - rect; - - if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) { - rect = element.getClientRect(); - - if ( x < rect.left ) { - range.setStartAt( element, CKEDITOR.POSITION_AFTER_START ); - range.collapse( true ); - } else { - range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END ); - range.collapse( true ); + // So we try to call moveToPoint with +-1px up to +-20px above or + // below original drop position to find nearest good drop position. + for ( var i = 0; i < 20 && !sucess; i++ ) { + if ( !sucess ) { + try { + $range.moveToPoint( x, y - i ); + sucess = true; + } catch ( err ) { + } + } + if ( !sucess ) { + try { + $range.moveToPoint( x, y + i ); + sucess = true; + } catch ( err ) { + } } } - // If drop happens on no element elementFromPoint returns html or body. - // - // * |Lorem ipsum pulvinar purus et euismod. - // - // vestibulum tincidunt augue eget tempus. - // - // * - drop position - // | - expected cursor position - // - // In such case we can try to use default selection. If startContainer is not - // 'editable' element it is probably proper selection. - else if ( defaultRange && defaultRange.startContainer && - !defaultRange.startContainer.equals( editor.editable() ) ) { - return defaultRange; - } - // Otherwise we can not find any drop position and we have to return null - // and cancel drop event. - else - return null; + if ( sucess ) { + var id = 'cke-temp-' + ( new Date() ).getTime(); + $range.pasteHTML( '\u200b' ); + + var span = editor.document.getById( id ); + range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); + span.remove(); + } else { + // If the fist method does not succeed we might be next to + // the short element (like header): + // + // Lorem ipsum pulvinar purus et euismod. + // + // + // SOME HEADER| * + // + // + // vestibulum tincidunt augue eget tempus. + // + // * - drop position + // | - expected cursor position + // + // In such situation elementFromPoint returns proper element. Using getClientRect + // it is possible to check if the cursor should be at the beginning or at the end + // of paragraph. + var $element = editor.document.$.elementFromPoint( x, y ), + element = new CKEDITOR.dom.element( $element ), + rect; + + if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) { + rect = element.getClientRect(); + + if ( x < rect.left ) { + range.setStartAt( element, CKEDITOR.POSITION_AFTER_START ); + range.collapse( true ); + } else { + range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END ); + range.collapse( true ); + } + } + // If drop happens on no element elementFromPoint returns html or body. + // + // * |Lorem ipsum pulvinar purus et euismod. + // + // vestibulum tincidunt augue eget tempus. + // + // * - drop position + // | - expected cursor position + // + // In such case we can try to use default selection. If startContainer is not + // 'editable' element it is probably proper selection. + else if ( defaultRange && defaultRange.startContainer && + !defaultRange.startContainer.equals( editor.editable() ) ) { + return defaultRange; + } + // Otherwise we can not find any drop position and we have to return null + // and cancel drop event. + else + return null; + + } + } catch ( err ) { + return null; } - } catch ( err ) { - return null; } - } - else - return null; + else + return null; - return range; - }; + return range; + }, - /** - * Initialize dataTransfer object based on the native drop event. If data - * transfer object was already initialized on this event then function will - * return that object. - * - * @param {Object} domEvent A native DOM drop event object. - * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. - * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object - * - */ - CKEDITOR.plugins.clipboard.initDataTransfer = function( evt, sourceEditor ) { - // Create a new dataTransfer object based on the drop event. - // If this event was used on dragstart to create dataTransfer - // both dataTransfer objects will have the same id. - var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( evt, sourceEditor ); - - // If there is the same id we will replace dataTransfer with the one - // created on drag, because it contains drag editor, drag content and so on. - // Otherwise (in case of drag from external source) we save new object to - // the global clipboard.dragData. - if ( CKEDITOR.plugins.clipboard.dragData && - dataTransfer.id == CKEDITOR.plugins.clipboard.dragData.id ) { - dataTransfer = CKEDITOR.plugins.clipboard.dragData; - } else { - CKEDITOR.plugins.clipboard.dragData = dataTransfer; - } + /** + * Initialize dataTransfer object based on the native drop event. If data + * transfer object was already initialized on this event then function will + * return that object. + * + * @param {Object} domEvent A native DOM drop event object. + * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. + * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object + * + */ + initDataTransfer: function( evt, sourceEditor ) { + // Create a new dataTransfer object based on the drop event. + // If this event was used on dragstart to create dataTransfer + // both dataTransfer objects will have the same id. + var dataTransfer = new this.dataTransfer( evt, sourceEditor ); + + // If there is the same id we will replace dataTransfer with the one + // created on drag, because it contains drag editor, drag content and so on. + // Otherwise (in case of drag from external source) we save new object to + // the global clipboard.dragData. + if ( this.dragData && + dataTransfer.id == this.dragData.id ) { + dataTransfer = this.dragData; + } else { + this.dragData = dataTransfer; + } - if ( sourceEditor ) { - CKEDITOR.plugins.clipboard.dragRange = sourceEditor.getSelection().getRanges()[ 0 ]; - } + if ( sourceEditor ) { + this.dragRange = sourceEditor.getSelection().getRanges()[ 0 ]; + } - return dataTransfer; - }; + return dataTransfer; + }, - /* - * Remove global dataTransfer object so the new dataTransfer - * will be not linked with the old one. - */ - CKEDITOR.plugins.clipboard.resetDataTransfer = function() { - CKEDITOR.plugins.clipboard.dragData = null; + /* + * Remove global dataTransfer object so the new dataTransfer + * will be not linked with the old one. + */ + resetDataTransfer: function() { + this.dragData = null; + } }; // Data type used to link drag and drop events. From 448f9eef111c66b0a32c30b46c9c2dbd08afde9c Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 13:50:56 +0200 Subject: [PATCH 58/67] Renamed fixIESplittedNodes to fixIESplitNodesAfterDrop. --- plugins/clipboard/plugin.js | 4 ++-- tests/plugins/clipboard/drop.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index cf11cdc9459..4242110ad0a 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1269,7 +1269,7 @@ editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - clipboard.fixIESplittedNodes( dragRange, dropRange ); + clipboard.fixIESplitNodesAfterDrop( dragRange, dropRange ); } // Because we manipulate multiple ranges we need to do it carefully, @@ -1388,7 +1388,7 @@ * * @private */ - fixIESplittedNodes: function( dragRange, dropRange ) { + fixIESplitNodesAfterDrop: function( dragRange, dropRange ) { if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && dropRange.startOffset > 0 && dropRange.startContainer.getChildCount() > dropRange.startOffset - 1 && diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index c18dc70037b..d5432969d86 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -368,7 +368,7 @@ var editors, editorBots, dropRange.collapse( true ); // Fix nodes. - CKEDITOR.plugins.clipboard.fixIESplittedNodes( dragRange, dropRange ); + CKEDITOR.plugins.clipboard.fixIESplitNodesAfterDrop( dragRange, dropRange ); // Asserts. assert.areSame( 1, p.getChildCount() ); From 84846850b7a14f1039ab09eb1c5733438b5cce82 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 14:14:26 +0200 Subject: [PATCH 59/67] Improved docs. --- plugins/clipboard/plugin.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 4242110ad0a..f84ead3aab2 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1387,6 +1387,8 @@ * This function is in the public scope for tests usage only. * * @private + * @param {CKEDITOR.dom.range} dragRange The drag range. + * @param {CKEDITOR.dom.range} dropRange The drop range. */ fixIESplitNodesAfterDrop: function( dragRange, dropRange ) { if ( dropRange.startContainer.type == CKEDITOR.NODE_ELEMENT && @@ -1417,6 +1419,9 @@ * This function is in the public scope for tests usage only. * * @private + * @param {CKEDITOR.dom.range} firstRange The first range to compare. + * @param {CKEDITOR.dom.range} secondRange The second range to compare. + * @returns {Boolean} True if the first range in before the second range. */ rangeBefore: function( firstRange, secondRange ) { // Both ranges has the same parent and the first has smaller offset. E.g.: @@ -1450,7 +1455,6 @@ * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} editor The source editor instance. * @returns {CKEDITOR.dom.range} range at drop position. - * */ getRangeAtDropPosition: function( dropEvt, editor ) { // If we drop content from the external source we need to call focus on IE. @@ -1599,7 +1603,6 @@ * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object - * */ initDataTransfer: function( evt, sourceEditor ) { // Create a new dataTransfer object based on the drop event. @@ -1632,6 +1635,21 @@ resetDataTransfer: function() { this.dragData = null; } + + /** + * Global object to save data for drag and drop. Object must be global to handle + * drag and drop from one CKEditor to the other. + * + * @private + * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData + */ + + /** + * Range object to save drag range and remove it after drop. + * + * @private + * @property {CKEDITOR.dom.range} dragRange + */ }; // Data type used to link drag and drop events. From 101066610d9d52535f1c006db8526b081f699b19 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 14:17:09 +0200 Subject: [PATCH 60/67] Renamed rangeBefore to isRangeBefore. --- plugins/clipboard/plugin.js | 12 ++++++------ tests/plugins/clipboard/drop.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index f84ead3aab2..730ccb03185 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1261,7 +1261,7 @@ // Execute drop with a timeout because otherwise selection, after drop, // on IE is in the drag position, instead of drop position. setTimeout( function() { - var dragBookmark, dropBookmark, i, rangeBefore; + var dragBookmark, dropBookmark, i, isRangeBefore; // Save and lock snapshot so there will be only // one snapshot for both remove and insert content. @@ -1276,13 +1276,13 @@ // changing one range (event creating a bookmark) may make other invalid. // We need to change ranges into bookmarks so we can manipulate them easily in the future. // We can change the range which is later in the text before we change the preceding range. - // We call rangeBefore to test the order of ranges. - rangeBefore = clipboard.rangeBefore( dragRange, dropRange ); - if ( !rangeBefore ) { + // We call isRangeBefore to test the order of ranges. + isRangeBefore = clipboard.isRangeBefore( dragRange, dropRange ); + if ( !isRangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); } dropBookmark = dropRange.clone().createBookmark( 1 ); - if ( rangeBefore ) { + if ( isRangeBefore ) { dragBookmark = dragRange.createBookmark( 1 ); } @@ -1423,7 +1423,7 @@ * @param {CKEDITOR.dom.range} secondRange The second range to compare. * @returns {Boolean} True if the first range in before the second range. */ - rangeBefore: function( firstRange, secondRange ) { + isRangeBefore: function( firstRange, secondRange ) { // Both ranges has the same parent and the first has smaller offset. E.g.: // // "Lorem ipsum dolor sit[1] amet consectetur[2] adipiscing elit." diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index d5432969d86..27a93864fe2 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -378,7 +378,7 @@ var editors, editorBots, assert.isInnerHtmlMatching( '

lorem^ ipsum sit amet.@

', getWithHtml( editor ), htmlMatchOpts ); }, - 'test rangeBefore 1': function() { + 'test isRangeBefore 1': function() { var editor = editors.framed, bot = editorBots[ editor.name ], firstRange = editor.createRange(), @@ -395,10 +395,10 @@ var editors, editorBots, secondRange.setStart( p.getChild( 0 ), 11 ); secondRange.collapse( true ); - assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); + assert.isTrue( CKEDITOR.plugins.clipboard.isRangeBefore( firstRange, secondRange ) ); }, - 'test rangeBefore 2': function() { + 'test isRangeBefore 2': function() { var editor = editors.framed, bot = editorBots[ editor.name ], firstRange = editor.createRange(), @@ -419,10 +419,10 @@ var editors, editorBots, secondRange.setStart( p, 2 ); secondRange.collapse( true ); - assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); + assert.isTrue( CKEDITOR.plugins.clipboard.isRangeBefore( firstRange, secondRange ) ); }, - 'test rangeBefore 3': function() { + 'test isRangeBefore 3': function() { var editor = editors.framed, bot = editorBots[ editor.name ], firstRange = editor.createRange(), @@ -441,7 +441,7 @@ var editors, editorBots, secondRange.setStart( p, 1 ); secondRange.collapse( true ); - assert.isTrue( CKEDITOR.plugins.clipboard.rangeBefore( firstRange, secondRange ) ); + assert.isTrue( CKEDITOR.plugins.clipboard.isRangeBefore( firstRange, secondRange ) ); } }; From d72f886fc8a01c3e96ad1020f13822768086195d Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 14:28:21 +0200 Subject: [PATCH 61/67] Renamed initDataTransfer to initDragDataTransfer and resetDataTransfer to resetDragDataTransfer. --- plugins/clipboard/plugin.js | 10 +++++----- tests/plugins/clipboard/datatransfer.js | 16 ++++++++-------- tests/plugins/clipboard/drop.js | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 730ccb03185..b26bd258629 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1221,14 +1221,14 @@ // and save range and selected HTML. editable.attachListener( dropTarget, 'dragstart', function( evt ) { // Create a dataTransfer object and save it globally. - clipboard.initDataTransfer( evt, editor ); + clipboard.initDragDataTransfer( evt, editor ); } ); // Clean up on dragend. editable.attachListener( dropTarget, 'dragend', function( evt ) { // When drag & drop is done we need to reset dataTransfer so the future // external drop will be not recognize as internal. - clipboard.resetDataTransfer(); + clipboard.resetDragDataTransfer(); } ); editable.attachListener( dropTarget, 'drop', function( evt ) { @@ -1236,7 +1236,7 @@ evt.data.preventDefault(); // Create dataTransfer of get it, if it was created before. - var dataTransfer = clipboard.initDataTransfer( evt ); + var dataTransfer = clipboard.initDragDataTransfer( evt ); dataTransfer.setTargetEditor( editor ); // Getting drop position is one of the most complex part. @@ -1604,7 +1604,7 @@ * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object */ - initDataTransfer: function( evt, sourceEditor ) { + initDragDataTransfer: function( evt, sourceEditor ) { // Create a new dataTransfer object based on the drop event. // If this event was used on dragstart to create dataTransfer // both dataTransfer objects will have the same id. @@ -1632,7 +1632,7 @@ * Remove global dataTransfer object so the new dataTransfer * will be not linked with the old one. */ - resetDataTransfer: function() { + resetDragDataTransfer: function() { this.dragData = null; } diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 04e92b1dcb2..c9ccc90b9a6 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -54,7 +54,7 @@ bender.test( { }, setUp: function() { - CKEDITOR.plugins.clipboard.resetDataTransfer(); + CKEDITOR.plugins.clipboard.resetDragDataTransfer(); }, 'test id': function() { @@ -172,29 +172,29 @@ bender.test( { }, - 'test initDataTransfer binding': function() { + 'test initDragDataTransfer binding': function() { var evt1 = createDragDropEventMock(), evt2 = createDragDropEventMock(), - dataTransferA = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ), - dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt1 ); + dataTransferA = CKEDITOR.plugins.clipboard.initDragDataTransfer( evt1 ), + dataTransferB = CKEDITOR.plugins.clipboard.initDragDataTransfer( evt1 ); assert.areSame( dataTransferA, dataTransferB, 'If we init dataTransfer object twice on the same event this should be the same object.' ); - CKEDITOR.plugins.clipboard.resetDataTransfer(); + CKEDITOR.plugins.clipboard.resetDragDataTransfer(); - dataTransferB = CKEDITOR.plugins.clipboard.initDataTransfer( evt2 ); + dataTransferB = CKEDITOR.plugins.clipboard.initDragDataTransfer( evt2 ); assert.areNotSame( dataTransferA, dataTransferB, 'If we init dataTransfer object twice on different events these should be different objects.' ); }, - 'test initDataTransfer constructor': function() { + 'test initDragDataTransfer constructor': function() { var bot = this.bots.editor1, editor = this.editors.editor1; bot.setHtmlWithSelection( '[xfoox]' ); var evt = createDragDropEventMock(), - dataTransfer = CKEDITOR.plugins.clipboard.initDataTransfer( evt, editor ); + dataTransfer = CKEDITOR.plugins.clipboard.initDragDataTransfer( evt, editor ); dataTransfer.setTargetEditor( editor ); assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransfer.getTransferType(), 'transferType' ); diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 27a93864fe2..23e76284bd6 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -115,7 +115,7 @@ var editors, editorBots, }, testsForMultipleEditor = { 'setUp': function() { - CKEDITOR.plugins.clipboard.resetDataTransfer(); + CKEDITOR.plugins.clipboard.resetDragDataTransfer(); }, 'test drop to header': function( editor ) { From 94e76bc0b7bf5ac6d54a2bfd94e24f93cc8f41f6 Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 14:37:21 +0200 Subject: [PATCH 62/67] Added todos. --- plugins/clipboard/plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index b26bd258629..f4e20b5a635 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1289,7 +1289,7 @@ // No we can safely delete content for the drag range... dragRange = editor.createRange(); dragRange.moveToBookmark( dragBookmark ); - dragRange.deleteContents(); + dragRange.deleteContents(); // @todo replace with the new delete content function // ...and paste content into the drop position. dropRange = editor.createRange(); @@ -1319,7 +1319,7 @@ // Remove dragged content and make a snapshot. dataTransfer.sourceEditor.fire( 'saveSnapshot' ); - dragRange.deleteContents(); + dragRange.deleteContents(); // @todo replace with the new delete content function dataTransfer.sourceEditor.getSelection().reset(); dataTransfer.sourceEditor.fire( 'saveSnapshot' ); @@ -1338,13 +1338,13 @@ firePasteWithDataTransfer( dataTransfer ); } + // @todo integrate with firePasteEvents. function firePasteWithDataTransfer( dataTransfer ) { if ( dataTransfer.dataValue ) { editor.fire( 'paste', dataTransfer ); } } - // Fix for Gecko bug with disappearing cursor. function fixGeckoDisappearingCursor() { editor.once( 'afterPaste', function() { @@ -1713,7 +1713,7 @@ if ( editor ) { this.sourceEditor = editor; - this.dataValue = editor.getSelection().getSelectedHtml(); + this.dataValue = editor.getSelection().getSelectedHtml(); // @todo replace with the new function this.dataType = 'html'; } else { // IE support only text data and throws exception if we try to get html data. From 3f5877fab99b4bedb2694719aa5ddc7d500d8732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Thu, 3 Jul 2014 15:36:10 +0200 Subject: [PATCH 63/67] JSLint issue. --- plugins/clipboard/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index f4e20b5a635..49d7b4437db 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1681,7 +1681,7 @@ this.id = this.$.getData( clipboardIdDataType ); function generateUniqueId() { - return ( new Date() ).getTime() + Math.random().toString( 16 ).substring( 2 ) + return ( new Date() ).getTime() + Math.random().toString( 16 ).substring( 2 ); } // If there is no ID we need to create it. Different browsers needs different ID. From a1a04cae7247586d2ccbc12659ca03922f4d76ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Thu, 3 Jul 2014 15:50:24 +0200 Subject: [PATCH 64/67] Fixed minor code style and docs issue. --- plugins/clipboard/plugin.js | 76 ++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 49d7b4437db..8b1f6b99ea2 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1368,24 +1368,25 @@ * Notify that first node on IE 8 & 9 is the original node object * but with shortened content. * - * Before: - * --- Text Node A ---------------------------------- - * /\ - * Drag position + * Before: + * --- Text Node A ---------------------------------- + * /\ + * Drag position * - * After (IE 8 & 9): - * --- Text Node A ----- --- Text Node B ----------- - * /\ /\ - * Drop position Drag position - * (invalid) + * After (IE 8 & 9): + * --- Text Node A ----- --- Text Node B ----------- + * /\ /\ + * Drop position Drag position + * (invalid) * - * After (other browsers): - * --- Text Node A ---------------------------------- - * /\ /\ - * Drop position Drag position + * After (other browsers): + * --- Text Node A ---------------------------------- + * /\ /\ + * Drop position Drag position * - * This function is in the public scope for tests usage only. + * **Note:** This function is in the public scope for tests usage only. * + * @since 4.5 * @private * @param {CKEDITOR.dom.range} dragRange The drag range. * @param {CKEDITOR.dom.range} dropRange The drop range. @@ -1410,14 +1411,15 @@ }, /** - * Check if the beginning of the firstRange is before the beginning of the secondRange - * and modification of the content in the firstRange may break secondRange. + * Check if the beginning of the `firstRange` is before the beginning of the `secondRange` + * and modification of the content in the `firstRange` may break `secondRange`. * - * Notify that this function returns false if these two ranges are in two - * separate nodes and do not affect each other (even if firstRange is before secondRange). + * Notify that this function returns `false` if these two ranges are in two + * separate nodes and do not affect each other (even if `firstRange` is before `secondRange`). * - * This function is in the public scope for tests usage only. + * **Note:** This function is in the public scope for tests usage only. * + * @since 4.5 * @private * @param {CKEDITOR.dom.range} firstRange The first range to compare. * @param {CKEDITOR.dom.range} secondRange The second range to compare. @@ -1447,11 +1449,9 @@ }, /** - * Get range from the drop event. - * - * Copy of getRangeAtDropPosition method from widget plugin. - * In #11219 method in widget should be removed and everything be according to DRY. + * Get range from the `drop` event. * + * @since 4.5 * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} editor The source editor instance. * @returns {CKEDITOR.dom.range} range at drop position. @@ -1600,6 +1600,7 @@ * transfer object was already initialized on this event then function will * return that object. * + * @since 4.5 * @param {Object} domEvent A native DOM drop event object. * @param {CKEDITOR.editor} [sourceEditor] The source editor instance. * @returns {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer object @@ -1631,6 +1632,8 @@ /* * Remove global dataTransfer object so the new dataTransfer * will be not linked with the old one. + * + * @since 4.5 */ resetDragDataTransfer: function() { this.dragData = null; @@ -1640,6 +1643,7 @@ * Global object to save data for drag and drop. Object must be global to handle * drag and drop from one CKEditor to the other. * + * @since 4.5 * @private * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData */ @@ -1647,6 +1651,7 @@ /** * Range object to save drag range and remove it after drop. * + * @since 4.5 * @private * @property {CKEDITOR.dom.range} dragRange */ @@ -1668,8 +1673,9 @@ * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences * between browsers. * + * @since 4.5 * @class CKEDITOR.plugins.clipboard.dataTransfer - * @constructor Creates a class instance and . + * @constructor Creates a class instance. * * @param {Object} domEvent A native DOM event object. * @param {CKEDITOR.editor} editor The source editor instance. If editor is defined then dataValue will be created based on the editor contents and dataType will be 'html'. @@ -1688,7 +1694,7 @@ if ( !this.id ) { if ( clipboardIdDataType == 'URL' ) { // For IEs URL type ID have to look like an URL. - this.id = 'http://cke.' + generateUniqueId() +'/'; + this.id = 'http://cke.' + generateUniqueId() + '/'; } else if ( clipboardIdDataType == 'Text' ) { // For IE10+ only Text data type is supported and we have to compare dragged // and dropped text. If the ID is not set it means that empty string was dragged @@ -1721,8 +1727,7 @@ try { this.dataValue = this.getData( 'text/html' ); this.dataType = 'html'; - } catch ( err ) { - } + } catch ( err ) {} if ( !this.dataValue ) { // Try to get text data otherwise. @@ -1736,17 +1741,19 @@ } } } + /** * Data transfer ID used to bind all dataTransfer * object based on the same event (ex. in drag and drop events). * + * @readonly * @property {String} id */ /** * A native DOM event object. * - * @private + * @readonly * @property {Object} $ */ @@ -1754,24 +1761,28 @@ * Source editor, the editor where drag starts. * Might be undefined if drag starts outside the editor (ex. dropping files to the editor). * - * @property {CKEDITOR.editor} [sourceEditor] + * @readonly + * @property {CKEDITOR.editor} sourceEditor */ /** * Target editor, the editor where drop occurred. * + * @readonly * @property {CKEDITOR.editor} targetEditor */ /** * HTML or text to be pasted. * + * @readonly * @property {String} dataValue */ /** * Type of data in `data.dataValue`. The value might be `html` or `text`. * + * @readonly * @property {String} dataType */ }; @@ -1780,6 +1791,7 @@ * Data transfer operation (drag and drop or copy and pasted) started and ended in the same * editor instance. * + * @since 4.5 * @readonly * @property {Number} [=0] * @member CKEDITOR @@ -1790,6 +1802,7 @@ * Data transfer operation (drag and drop or copy and pasted) started and ended in the * instance of CKEditor but in two different editors. * + * @since 4.5 * @readonly * @property {Number} [=1] * @member CKEDITOR @@ -1800,6 +1813,7 @@ * Data transfer operation (drag and drop or copy and pasted) started not in the CKEditor. * The source of the data may be textarea, HTML, another application, etc.. * + * @since 4.5 * @readonly * @property {Number} [=2] * @member CKEDITOR @@ -1839,8 +1853,8 @@ /** * Get data transfer type. * - * @returns {Number} type - * Possible options: DATA_TRANSFER_INTERNAL, DATA_TRANSFER_CROSS_EDITORS, DATA_TRANSFER_EXTERNAL. + * @returns {Number} Possible values: {@link CKEDITOR#DATA_TRANSFER_INTERNAL}, + * {@link CKEDITOR#DATA_TRANSFER_CROSS_EDITORS}, {@link CKEDITOR#DATA_TRANSFER_EXTERNAL}. */ getTransferType: function() { if ( !this.sourceEditor ) { From 7be5499cc4292abdb7383ae9d4697a77d5497f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Thu, 3 Jul 2014 16:09:58 +0200 Subject: [PATCH 65/67] Tests: Order of things matters - fixed test failing on IE9. --- tests/plugins/clipboard/drop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 23e76284bd6..3f45a2723ce 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -302,8 +302,8 @@ var editors, editorBots, var bot = editorBots[ editor.name ], evt = createDragDropEventMock(); - editor.resetUndo(); bot.setHtmlWithSelection( '

Lorem ^ipsum sit amet.

' ); + editor.resetUndo(); drop( editor, evt, { element: editor.document.getById( 'p' ).getChild( 0 ), From 285231656e2e4624225168cbf9cb9d0870072a3e Mon Sep 17 00:00:00 2001 From: Piotr Jasiun Date: Thu, 3 Jul 2014 16:10:42 +0200 Subject: [PATCH 66/67] Fixed drop to the textarea on Safari. --- plugins/clipboard/plugin.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 8b1f6b99ea2..29d4232eef1 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -1712,15 +1712,16 @@ this.$.setData( clipboardIdDataType, this.id ); } - // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. - if ( evt.name == 'dragstart' && CKEDITOR.env.safari ) { - evt.data.$.dataTransfer.setData( 'text', evt.data.$.dataTransfer.getData( 'text' ) ); - } - if ( editor ) { this.sourceEditor = editor; this.dataValue = editor.getSelection().getSelectedHtml(); // @todo replace with the new function this.dataType = 'html'; + + // Without setData( 'text', ... ) on dragstart there is no drop event in Safari. + // Also 'text' data is empty as drop to the textarea does not work if we do not put there text. + if ( evt.name == 'dragstart' && CKEDITOR.env.safari ) { + evt.data.$.dataTransfer.setData( 'text', editor.getSelection().getSelectedText() ); + } } else { // IE support only text data and throws exception if we try to get html data. // This html data object may also be empty if we drag content of the textarea. From 4c944961dd23fe13194b24a81abb64ee8bb99dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Thu, 3 Jul 2014 16:25:56 +0200 Subject: [PATCH 67/67] Tests: And making some selection matters too - this fixed test failing on IE9 for real. --- tests/plugins/clipboard/drop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/clipboard/drop.js b/tests/plugins/clipboard/drop.js index 3f45a2723ce..4b7d54a798b 100644 --- a/tests/plugins/clipboard/drop.js +++ b/tests/plugins/clipboard/drop.js @@ -320,7 +320,7 @@ var editors, editorBots, botCross = editorBots[ 'cross' ], editorCross = botCross.editor; - setWithHtml( bot.editor, '

Lorem ipsum sit amet.

' ); + setWithHtml( bot.editor, '

{}Lorem ipsum sit amet.

' ); setWithHtml( botCross.editor, '

Lorem {ipsum dolor }sit amet.

' ); bot.editor.resetUndo(); botCross.editor.resetUndo();