aboutsummaryrefslogtreecommitdiff
path: root/sources/plugins/clipboard/plugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'sources/plugins/clipboard/plugin.js')
-rw-r--r--sources/plugins/clipboard/plugin.js332
1 files changed, 170 insertions, 162 deletions
diff --git a/sources/plugins/clipboard/plugin.js b/sources/plugins/clipboard/plugin.js
index 5c387b3..433f547 100644
--- a/sources/plugins/clipboard/plugin.js
+++ b/sources/plugins/clipboard/plugin.js
@@ -44,7 +44,6 @@
44// -- Paste command 44// -- Paste command
45// * fire 'paste' on editable ('beforepaste' for IE) 45// * fire 'paste' on editable ('beforepaste' for IE)
46// * !canceled && execCommand 'paste' 46// * !canceled && execCommand 'paste'
47// * !success && fire 'pasteDialog' on editor
48// -- Paste from native context menu & menubar 47// -- Paste from native context menu & menubar
49// (Fx & Webkits are handled in 'paste' default listener. 48// (Fx & Webkits are handled in 'paste' default listener.
50// Opera cannot be handled at all because it doesn't fire any events 49// Opera cannot be handled at all because it doesn't fire any events
@@ -117,9 +116,9 @@
117( function() { 116( function() {
118 // Register the plugin. 117 // Register the plugin.
119 CKEDITOR.plugins.add( 'clipboard', { 118 CKEDITOR.plugins.add( 'clipboard', {
120 requires: 'dialog', 119 requires: 'notification,toolbar',
121 // jscs:disable maximumLineLength 120 // jscs:disable maximumLineLength
122 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% 121 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
123 // jscs:enable maximumLineLength 122 // jscs:enable maximumLineLength
124 icons: 'copy,copy-rtl,cut,cut-rtl,paste,paste-rtl', // %REMOVE_LINE_CORE% 123 icons: 'copy,copy-rtl,cut,cut-rtl,paste,paste-rtl', // %REMOVE_LINE_CORE%
125 hidpi: true, // %REMOVE_LINE_CORE% 124 hidpi: true, // %REMOVE_LINE_CORE%
@@ -143,8 +142,6 @@
143 initPasteClipboard( editor ); 142 initPasteClipboard( editor );
144 initDragDrop( editor ); 143 initDragDrop( editor );
145 144
146 CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );
147
148 // Convert image file (if present) to base64 string for Firefox. Do it as the first 145 // Convert image file (if present) to base64 string for Firefox. Do it as the first
149 // step as the conversion is asynchronous and should hold all further paste processing. 146 // step as the conversion is asynchronous and should hold all further paste processing.
150 if ( CKEDITOR.env.gecko ) { 147 if ( CKEDITOR.env.gecko ) {
@@ -156,7 +153,7 @@
156 data = dataObj.dataValue, 153 data = dataObj.dataValue,
157 dataTransfer = dataObj.dataTransfer; 154 dataTransfer = dataObj.dataTransfer;
158 155
159 // If data empty check for image content inside data transfer. #16705 156 // If data empty check for image content inside data transfer. http://dev.ckeditor.com/ticket/16705
160 if ( !data && dataObj.method == 'paste' && dataTransfer && dataTransfer.getFilesCount() == 1 && latestId != dataTransfer.id ) { 157 if ( !data && dataObj.method == 'paste' && dataTransfer && dataTransfer.getFilesCount() == 1 && latestId != dataTransfer.id ) {
161 var file = dataTransfer.getFile( 0 ); 158 var file = dataTransfer.getFile( 0 );
162 159
@@ -250,14 +247,14 @@
250 data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' ); 247 data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
251 } 248 }
252 249
253 // Strip editable that was copied from inside. (#9534) 250 // Strip editable that was copied from inside. (http://dev.ckeditor.com/ticket/9534)
254 if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) { 251 if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) {
255 var tmp, 252 var tmp,
256 editable_wrapper, 253 editable_wrapper,
257 wrapper = new CKEDITOR.dom.element( 'div' ); 254 wrapper = new CKEDITOR.dom.element( 'div' );
258 255
259 wrapper.setHtml( data ); 256 wrapper.setHtml( data );
260 // Verify for sure and check for nested editor UI parts. (#9675) 257 // Verify for sure and check for nested editor UI parts. (http://dev.ckeditor.com/ticket/9675)
261 while ( wrapper.getChildCount() == 1 && 258 while ( wrapper.getChildCount() == 1 &&
262 ( tmp = wrapper.getFirst() ) && 259 ( tmp = wrapper.getFirst() ) &&
263 tmp.type == CKEDITOR.NODE_ELEMENT && // Make sure first-child is element. 260 tmp.type == CKEDITOR.NODE_ELEMENT && // Make sure first-child is element.
@@ -299,7 +296,7 @@
299 296
300 editor.on( 'paste', function( evt ) { 297 editor.on( 'paste', function( evt ) {
301 var dataObj = evt.data, 298 var dataObj = evt.data,
302 type = dataObj.type, 299 type = editor._.nextPasteType || dataObj.type,
303 data = dataObj.dataValue, 300 data = dataObj.dataValue,
304 trueType, 301 trueType,
305 // Default is 'html'. 302 // Default is 'html'.
@@ -313,13 +310,15 @@
313 trueType = recogniseContentType( data ); 310 trueType = recogniseContentType( data );
314 } 311 }
315 312
313 delete editor._.nextPasteType;
314
316 // Unify text markup. 315 // Unify text markup.
317 if ( trueType == 'htmlifiedtext' ) { 316 if ( trueType == 'htmlifiedtext' ) {
318 data = htmlifiedTextHtmlification( editor.config, data ); 317 data = htmlifiedTextHtmlification( editor.config, data );
319 } 318 }
320 319
321 // Strip presentational markup & unify text markup. 320 // Strip presentational markup & unify text markup.
322 // Forced plain text (dialog or forcePAPT). 321 // Forced plain text.
323 // Note: we do not check dontFilter option in this case, because forcePAPT was implemented 322 // Note: we do not check dontFilter option in this case, because forcePAPT was implemented
324 // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so 323 // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
325 // forcePAPT should have priority as it had before 4.5. 324 // forcePAPT should have priority as it had before 4.5.
@@ -364,17 +363,6 @@
364 }, 0 ); 363 }, 0 );
365 } 364 }
366 }, null, null, 1000 ); 365 }, null, null, 1000 );
367
368 editor.on( 'pasteDialog', function( evt ) {
369 // TODO it's possible that this setTimeout is not needed any more,
370 // because of changes introduced in the same commit as this comment.
371 // Editor.getClipboardData adds listener to the dialog's events which are
372 // fired after a while (not like 'showDialog').
373 setTimeout( function() {
374 // Open default paste dialog.
375 editor.openDialog( 'paste', evt.data );
376 }, 0 );
377 } );
378 } 366 }
379 } ); 367 } );
380 368
@@ -402,8 +390,8 @@
402 } 390 }
403 391
404 // Because of FF bug we need to use this hack, otherwise cursor is hidden 392 // Because of FF bug we need to use this hack, otherwise cursor is hidden
405 // or it is not possible to move it (#12420). 393 // or it is not possible to move it (http://dev.ckeditor.com/ticket/12420).
406 // Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (#13305). 394 // Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (http://dev.ckeditor.com/ticket/13305).
407 if ( CKEDITOR.env.gecko && data.method == 'drop' && editor.toolbox ) { 395 if ( CKEDITOR.env.gecko && data.method == 'drop' && editor.toolbox ) {
408 editor.once( 'afterPaste', function() { 396 editor.once( 'afterPaste', function() {
409 editor.toolbox.focus(); 397 editor.toolbox.focus();
@@ -423,28 +411,25 @@
423 addButtonsCommands(); 411 addButtonsCommands();
424 412
425 /** 413 /**
426 * Gets clipboard data by directly accessing the clipboard (IE only) or opening the paste dialog window. 414 * Gets clipboard data by directly accessing the clipboard (IE only).
427 * 415 *
428 * editor.getClipboardData( { title: 'Get my data' }, function( data ) { 416 * editor.getClipboardData( function( data ) {
429 * if ( data ) 417 * if ( data )
430 * alert( data.type + ' ' + data.dataValue ); 418 * alert( data.type + ' ' + data.dataValue );
431 * } ); 419 * } );
432 * 420 *
433 * @member CKEDITOR.editor 421 * @member CKEDITOR.editor
434 * @param {Object} options 422 * @param {Function/Object} callbackOrOptions For function, see the `callback` parameter documentation. The object was used before 4.7.0 with the `title` property, to set the paste dialog's title.
435 * @param {String} [options.title] The title of the paste dialog window. 423 * @param {Function} callback A function that will be executed with the `data` property of the
436 * @param {Function} callback A function that will be executed with `data.type` and `data.dataValue` 424 * {@link CKEDITOR.editor#event-paste paste event} or `null` if none of the capturing methods succeeded.
437 * or `null` if none of the capturing methods succeeded. 425 * Since 4.7.0 the `callback` should be provided as a first argument, just like in the example above. This parameter will be removed in
426 * an upcoming major release.
438 */ 427 */
439 editor.getClipboardData = function( options, callback ) { 428 editor.getClipboardData = function( callbackOrOptions, callback ) {
440 var beforePasteNotCanceled = false,
441 dataType = 'auto',
442 dialogCommited = false;
443
444 // Options are optional - args shift. 429 // Options are optional - args shift.
445 if ( !callback ) { 430 if ( !callback ) {
446 callback = options; 431 callback = callbackOrOptions;
447 options = null; 432 callbackOrOptions = null;
448 } 433 }
449 434
450 // Listen with maximum priority to handle content before everyone else. 435 // Listen with maximum priority to handle content before everyone else.
@@ -452,39 +437,13 @@
452 // access to the clipboard succeed in IE. 437 // access to the clipboard succeed in IE.
453 editor.on( 'paste', onPaste, null, null, 0 ); 438 editor.on( 'paste', onPaste, null, null, 0 );
454 439
455 // Listen at the end of listeners chain to see if event wasn't canceled
456 // and to retrieve modified data.type.
457 editor.on( 'beforePaste', onBeforePaste, null, null, 1000 );
458
459 // getClipboardDataDirectly() will fire 'beforePaste' synchronously, so we can
460 // check if it was canceled and if any listener modified data.type.
461
462 // If command didn't succeed (only IE allows to access clipboard and only if 440 // If command didn't succeed (only IE allows to access clipboard and only if
463 // user agrees) open and handle paste dialog. 441 // user agrees) invoke callback with null, meaning that paste is not blocked.
464 if ( getClipboardDataDirectly() === false ) { 442 if ( getClipboardDataDirectly() === false ) {
465 // Direct access to the clipboard wasn't successful so remove listener. 443 // Direct access to the clipboard wasn't successful so remove listener.
466 editor.removeListener( 'paste', onPaste ); 444 editor.removeListener( 'paste', onPaste );
467 445
468 // If beforePaste was canceled do not open dialog. 446 callback( null );
469 // Add listeners only if dialog really opened. 'pasteDialog' can be canceled.
470 if ( beforePasteNotCanceled && editor.fire( 'pasteDialog', onDialogOpen ) ) {
471 editor.on( 'pasteDialogCommit', onDialogCommit );
472
473 // 'dialogHide' will be fired after 'pasteDialogCommit'.
474 editor.on( 'dialogHide', function( evt ) {
475 evt.removeListener();
476 evt.data.removeListener( 'pasteDialogCommit', onDialogCommit );
477
478 // Because Opera has to wait a while in pasteDialog we have to wait here.
479 setTimeout( function() {
480 // Notify even if user canceled dialog (clicked 'cancel', ESC, etc).
481 if ( !dialogCommited )
482 callback( null );
483 }, 10 );
484 } );
485 } else {
486 callback( null );
487 }
488 } 447 }
489 448
490 function onPaste( evt ) { 449 function onPaste( evt ) {
@@ -492,30 +451,6 @@
492 evt.cancel(); 451 evt.cancel();
493 callback( evt.data ); 452 callback( evt.data );
494 } 453 }
495
496 function onBeforePaste( evt ) {
497 evt.removeListener();
498 beforePasteNotCanceled = true;
499 dataType = evt.data.type;
500 }
501
502 function onDialogCommit( evt ) {
503 evt.removeListener();
504 // Cancel pasteDialogCommit so paste dialog won't automatically fire
505 // 'paste' evt by itself.
506 evt.cancel();
507 dialogCommited = true;
508 callback( {
509 type: dataType,
510 dataValue: evt.data.dataValue,
511 dataTransfer: evt.data.dataTransfer,
512 method: 'paste'
513 } );
514 }
515
516 function onDialogOpen() {
517 this.customTitle = ( options && options.title );
518 }
519 }; 454 };
520 455
521 function addButtonsCommands() { 456 function addButtonsCommands() {
@@ -574,7 +509,7 @@
574 509
575 if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) { 510 if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
576 var initOnCopyCut = function( evt ) { 511 var initOnCopyCut = function( evt ) {
577 // If user tries to cut in read-only editor, we must prevent default action. (#13872) 512 // If user tries to cut in read-only editor, we must prevent default action. (http://dev.ckeditor.com/ticket/13872)
578 if ( !editor.readOnly || evt.name != 'cut' ) { 513 if ( !editor.readOnly || evt.name != 'cut' ) {
579 clipboard.initPasteDataTransfer( evt, editor ); 514 clipboard.initPasteDataTransfer( evt, editor );
580 } 515 }
@@ -586,7 +521,7 @@
586 521
587 // Delete content with the low priority so one can overwrite cut data. 522 // Delete content with the low priority so one can overwrite cut data.
588 editable.on( 'cut', function() { 523 editable.on( 'cut', function() {
589 // If user tries to cut in read-only editor, we must prevent default action. (#13872) 524 // If user tries to cut in read-only editor, we must prevent default action. (http://dev.ckeditor.com/ticket/13872)
590 if ( !editor.readOnly ) { 525 if ( !editor.readOnly ) {
591 editor.extractSelectedHtml(); 526 editor.extractSelectedHtml();
592 } 527 }
@@ -663,17 +598,15 @@
663 pasteDataFromClipboard( evt ); 598 pasteDataFromClipboard( evt );
664 599
665 // Force IE to paste content into pastebin so pasteDataFromClipboard will work. 600 // Force IE to paste content into pastebin so pasteDataFromClipboard will work.
666 if ( !execIECommand( 'paste' ) ) { 601 execIECommand( 'paste' );
667 editor.openDialog( 'paste' );
668 }
669 } ); 602 } );
670 603
671 // If mainPasteEvent is 'beforePaste' (IE before Edge), 604 // If mainPasteEvent is 'beforePaste' (IE before Edge),
672 // dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953) 605 // dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (http://dev.ckeditor.com/ticket/7953)
673 editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 ); 606 editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 );
674 607
675 editable.on( 'beforepaste', function( evt ) { 608 editable.on( 'beforepaste', function( evt ) {
676 // Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (#11970). 609 // Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (http://dev.ckeditor.com/ticket/11970).
677 if ( evt.data && !evt.data.$.ctrlKey && !evt.data.$.shiftKey ) 610 if ( evt.data && !evt.data.$.ctrlKey && !evt.data.$.shiftKey )
678 preventBeforePasteEventNow(); 611 preventBeforePasteEventNow();
679 }, null, null, 0 ); 612 }, null, null, 0 );
@@ -687,7 +620,7 @@
687 620
688 // Use editor.document instead of editable in non-IEs for observing mouseup 621 // Use editor.document instead of editable in non-IEs for observing mouseup
689 // since editable won't fire the event if selection process started within 622 // since editable won't fire the event if selection process started within
690 // iframe and ended out of the editor (#9851). 623 // iframe and ended out of the editor (http://dev.ckeditor.com/ticket/9851).
691 editable.attachListener( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement(), 'mouseup', function() { 624 editable.attachListener( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement(), 'mouseup', function() {
692 mouseupTimeout = setTimeout( function() { 625 mouseupTimeout = setTimeout( function() {
693 setToolbarStates(); 626 setToolbarStates();
@@ -696,7 +629,7 @@
696 629
697 // Make sure that deferred mouseup callback isn't executed after editor instance 630 // Make sure that deferred mouseup callback isn't executed after editor instance
698 // had been destroyed. This may happen when editor.destroy() is called in parallel 631 // had been destroyed. This may happen when editor.destroy() is called in parallel
699 // with mouseup event (i.e. a button with onclick callback) (#10219). 632 // with mouseup event (i.e. a button with onclick callback) (http://dev.ckeditor.com/ticket/10219).
700 editor.on( 'destroy', function() { 633 editor.on( 'destroy', function() {
701 clearTimeout( mouseupTimeout ); 634 clearTimeout( mouseupTimeout );
702 } ); 635 } );
@@ -746,27 +679,73 @@
746 canUndo: false, 679 canUndo: false,
747 async: true, 680 async: true,
748 fakeKeystroke: CKEDITOR.CTRL + 86 /*V*/, 681 fakeKeystroke: CKEDITOR.CTRL + 86 /*V*/,
682
683 /**
684 * The default implementation of the paste command.
685 *
686 * @private
687 * @param {CKEDITOR.editor} editor An instance of the editor where the command is being executed.
688 * @param {Object/String} data If `data` is a string, then it is considered content that is being pasted.
689 * Otherwise it is treated as an object with options.
690 * @param {Boolean/String} [data.notification=true] Content for a notification shown after an unsuccessful
691 * paste attempt. If `false`, the notification will not be displayed. This parameter was added in 4.7.0.
692 * @param {String} [data.type='html'] The type of pasted content. There are two allowed values:
693 * * 'html'
694 * * 'text'
695 * @param {String/Object} data.dataValue Content being pasted. If this parameter is an object, it
696 * is supposed to be a `data` property of the {@link CKEDITOR.editor#paste} event.
697 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer Data transfer instance connected
698 * with the current paste action.
699 * @member CKEDITOR.editor.commands.paste
700 */
749 exec: function( editor, data ) { 701 exec: function( editor, data ) {
702 data = typeof data !== 'undefined' && data !== null ? data : {};
703
750 var cmd = this, 704 var cmd = this,
751 fire = function( data, withBeforePaste ) { 705 notification = typeof data.notification !== 'undefined' ? data.notification : true,
752 data && firePasteEvents( editor, data, !!withBeforePaste ); 706 forcedType = data.type,
753 707 keystroke = CKEDITOR.tools.keystrokeToString( editor.lang.common.keyboard,
754 editor.fire( 'afterCommandExec', { 708 editor.getCommandKeystroke( this ) ),
755 name: 'paste', 709 msg = typeof notification === 'string' ? notification : editor.lang.clipboard.pasteNotification
756 command: cmd, 710 .replace( /%1/, '<kbd aria-label="' + keystroke.aria + '">' + keystroke.display + '</kbd>' ),
757 returnValue: !!data 711 pastedContent = typeof data === 'string' ? data : data.dataValue;
758 } ); 712
759 }; 713 function callback( data, withBeforePaste ) {
760 714 withBeforePaste = typeof withBeforePaste !== 'undefined' ? withBeforePaste : true;
761 // Check data precisely - don't open dialog on empty string. 715
762 if ( typeof data == 'string' ) 716 if ( data ) {
763 fire( { 717 data.method = 'paste';
764 dataValue: data, 718
765 method: 'paste', 719 if ( !data.dataTransfer ) {
766 dataTransfer: clipboard.initPasteDataTransfer() 720 data.dataTransfer = clipboard.initPasteDataTransfer();
767 }, 1 ); 721 }
768 else 722
769 editor.getClipboardData( fire ); 723 firePasteEvents( editor, data, withBeforePaste );
724 } else if ( notification ) {
725 editor.showNotification( msg, 'info', editor.config.clipboard_notificationDuration );
726 }
727
728 editor.fire( 'afterCommandExec', {
729 name: 'paste',
730 command: cmd,
731 returnValue: !!data
732 } );
733 }
734
735 // Force type for the next paste.
736 if ( forcedType ) {
737 editor._.nextPasteType = forcedType;
738 } else {
739 delete editor._.nextPasteType;
740 }
741
742 if ( typeof pastedContent === 'string' ) {
743 callback( {
744 dataValue: pastedContent
745 } );
746 } else {
747 editor.getClipboardData( callback );
748 }
770 } 749 }
771 }; 750 };
772 } 751 }
@@ -820,7 +799,7 @@
820 return enabled; 799 return enabled;
821 } 800 }
822 801
823 // Cutting off control type element in IE standards breaks the selection entirely. (#4881) 802 // Cutting off control type element in IE standards breaks the selection entirely. (http://dev.ckeditor.com/ticket/4881)
824 function fixCut() { 803 function fixCut() {
825 if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks ) 804 if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks )
826 return; 805 return;
@@ -857,14 +836,14 @@
857 }, 836 },
858 blurListener; 837 blurListener;
859 838
860 // Avoid recursions on 'paste' event or consequent paste too fast. (#5730) 839 // Avoid recursions on 'paste' event or consequent paste too fast. (http://dev.ckeditor.com/ticket/5730)
861 if ( doc.getById( 'cke_pastebin' ) ) 840 if ( doc.getById( 'cke_pastebin' ) )
862 return; 841 return;
863 842
864 var sel = editor.getSelection(); 843 var sel = editor.getSelection();
865 var bms = sel.createBookmarks(); 844 var bms = sel.createBookmarks();
866 845
867 // #11384. On IE9+ we use native selectionchange (i.e. editor#selectionCheck) to cache the most 846 // http://dev.ckeditor.com/ticket/11384. On IE9+ we use native selectionchange (i.e. editor#selectionCheck) to cache the most
868 // recent selection which we then lock on editable blur. See selection.js for more info. 847 // recent selection which we then lock on editable blur. See selection.js for more info.
869 // selectionchange fired before getClipboardDataByPastebin() cached selection 848 // selectionchange fired before getClipboardDataByPastebin() cached selection
870 // before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM), 849 // before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM),
@@ -898,7 +877,7 @@
898 // It's better to paste close to the real paste destination, so inherited styles 877 // It's better to paste close to the real paste destination, so inherited styles
899 // (which Webkits will try to compensate by styling span) differs less from the destination's one. 878 // (which Webkits will try to compensate by styling span) differs less from the destination's one.
900 editable.append( pastebin ); 879 editable.append( pastebin );
901 // Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754) 880 // Style pastebin like .cke_editable, to minimize differences between origin and destination. (http://dev.ckeditor.com/ticket/9754)
902 pastebin.addClass( 'cke_editable' ); 881 pastebin.addClass( 'cke_editable' );
903 882
904 // Compensate position of offsetParent. 883 // Compensate position of offsetParent.
@@ -932,7 +911,7 @@
932 padding: 0 911 padding: 0
933 } ); 912 } );
934 913
935 // Paste fails in Safari when the body tag has 'user-select: none'. (#12506) 914 // Paste fails in Safari when the body tag has 'user-select: none'. (http://dev.ckeditor.com/ticket/12506)
936 if ( CKEDITOR.env.safari ) 915 if ( CKEDITOR.env.safari )
937 pastebin.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'text' ) ); 916 pastebin.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'text' ) );
938 917
@@ -955,8 +934,8 @@
955 934
956 // Webkit fill fire blur on editable when moving selection to 935 // Webkit fill fire blur on editable when moving selection to
957 // pastebin (if body is used). Cancel it because it causes incorrect 936 // pastebin (if body is used). Cancel it because it causes incorrect
958 // selection lock in case of inline editor (#10644). 937 // selection lock in case of inline editor (http://dev.ckeditor.com/ticket/10644).
959 // The same seems to apply to Firefox (#10787). 938 // The same seems to apply to Firefox (http://dev.ckeditor.com/ticket/10787).
960 if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) 939 if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko )
961 blurListener = editable.once( 'blur', cancel, null, null, -100 ); 940 blurListener = editable.once( 'blur', cancel, null, null, -100 );
962 941
@@ -969,7 +948,7 @@
969 // If non-native paste is executed, IE will open security alert and blur editable. 948 // If non-native paste is executed, IE will open security alert and blur editable.
970 // Editable will then lock selection inside itself and after accepting security alert 949 // Editable will then lock selection inside itself and after accepting security alert
971 // this selection will be restored. We overwrite stored selection, so it's restored 950 // this selection will be restored. We overwrite stored selection, so it's restored
972 // in pastebin. (#9552) 951 // in pastebin. (http://dev.ckeditor.com/ticket/9552)
973 if ( CKEDITOR.env.ie ) { 952 if ( CKEDITOR.env.ie ) {
974 blurListener = editable.once( 'blur', function() { 953 blurListener = editable.once( 'blur', function() {
975 editor.lockSelection( selPastebin ); 954 editor.lockSelection( selPastebin );
@@ -981,18 +960,18 @@
981 // Wait a while and grab the pasted contents. 960 // Wait a while and grab the pasted contents.
982 setTimeout( function() { 961 setTimeout( function() {
983 // Restore main window's scroll position which could have been changed 962 // Restore main window's scroll position which could have been changed
984 // by browser in cases described in #9771. 963 // by browser in cases described in http://dev.ckeditor.com/ticket/9771.
985 if ( CKEDITOR.env.webkit ) 964 if ( CKEDITOR.env.webkit )
986 CKEDITOR.document.getBody().$.scrollTop = scrollTop; 965 CKEDITOR.document.getBody().$.scrollTop = scrollTop;
987 966
988 // Blur will be fired only on non-native paste. In other case manually remove listener. 967 // Blur will be fired only on non-native paste. In other case manually remove listener.
989 blurListener && blurListener.removeListener(); 968 blurListener && blurListener.removeListener();
990 969
991 // Restore properly the document focus. (#8849) 970 // Restore properly the document focus. (http://dev.ckeditor.com/ticket/8849)
992 if ( CKEDITOR.env.ie ) 971 if ( CKEDITOR.env.ie )
993 editable.focus(); 972 editable.focus();
994 973
995 // IE7: selection must go before removing pastebin. (#8691) 974 // IE7: selection must go before removing pastebin. (http://dev.ckeditor.com/ticket/8691)
996 sel.selectBookmarks( bms ); 975 sel.selectBookmarks( bms );
997 pastebin.remove(); 976 pastebin.remove();
998 977
@@ -1018,7 +997,6 @@
1018 // On other browsers we should fire beforePaste event and return false. 997 // On other browsers we should fire beforePaste event and return false.
1019 function getClipboardDataDirectly() { 998 function getClipboardDataDirectly() {
1020 if ( clipboard.mainPasteEvent == 'paste' ) { 999 if ( clipboard.mainPasteEvent == 'paste' ) {
1021 // beforePaste should be fired when dialog open so it can be canceled.
1022 editor.fire( 'beforePaste', { type: 'auto', method: 'paste' } ); 1000 editor.fire( 'beforePaste', { type: 'auto', method: 'paste' } );
1023 return false; 1001 return false;
1024 } 1002 }
@@ -1031,7 +1009,7 @@
1031 // we're canceling it. 1009 // we're canceling it.
1032 preventPasteEventNow(); 1010 preventPasteEventNow();
1033 1011
1034 // #9247: Lock focus to prevent IE from hiding toolbar for inline editor. 1012 // http://dev.ckeditor.com/ticket/9247: Lock focus to prevent IE from hiding toolbar for inline editor.
1035 var focusManager = editor.focusManager; 1013 var focusManager = editor.focusManager;
1036 focusManager.lock(); 1014 focusManager.lock();
1037 1015
@@ -1074,7 +1052,7 @@
1074 editor.fire( 'saveSnapshot' ); // Save before cut 1052 editor.fire( 'saveSnapshot' ); // Save before cut
1075 setTimeout( function() { 1053 setTimeout( function() {
1076 editor.fire( 'saveSnapshot' ); // Save after cut 1054 editor.fire( 'saveSnapshot' ); // Save after cut
1077 }, 50 ); // OSX is slow (#11416). 1055 }, 50 ); // OSX is slow (http://dev.ckeditor.com/ticket/11416).
1078 } 1056 }
1079 } 1057 }
1080 1058
@@ -1327,7 +1305,7 @@
1327 1305
1328 // -------------- DRAGOVER TOP & BOTTOM -------------- 1306 // -------------- DRAGOVER TOP & BOTTOM --------------
1329 1307
1330 // Not allowing dragging on toolbar and bottom (#12613). 1308 // Not allowing dragging on toolbar and bottom (http://dev.ckeditor.com/ticket/12613).
1331 clipboard.preventDefaultDropOnElement( top ); 1309 clipboard.preventDefaultDropOnElement( top );
1332 clipboard.preventDefaultDropOnElement( bottom ); 1310 clipboard.preventDefaultDropOnElement( bottom );
1333 1311
@@ -1349,7 +1327,7 @@
1349 // Save drag range globally for cross editor D&D. 1327 // Save drag range globally for cross editor D&D.
1350 var dragRange = clipboard.dragRange = editor.getSelection().getRanges()[ 0 ]; 1328 var dragRange = clipboard.dragRange = editor.getSelection().getRanges()[ 0 ];
1351 1329
1352 // Store number of children, so we can later tell if any text node was split on drop. (#13011, #13447) 1330 // Store number of children, so we can later tell if any text node was split on drop. (http://dev.ckeditor.com/ticket/13011, http://dev.ckeditor.com/ticket/13447)
1353 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { 1331 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
1354 clipboard.dragStartContainerChildCount = dragRange ? getContainerChildCount( dragRange.startContainer ) : null; 1332 clipboard.dragStartContainerChildCount = dragRange ? getContainerChildCount( dragRange.startContainer ) : null;
1355 clipboard.dragEndContainerChildCount = dragRange ? getContainerChildCount( dragRange.endContainer ) : null; 1333 clipboard.dragEndContainerChildCount = dragRange ? getContainerChildCount( dragRange.endContainer ) : null;
@@ -1373,9 +1351,15 @@
1373 // we drop image it will overwrite document. 1351 // we drop image it will overwrite document.
1374 1352
1375 editable.attachListener( dropTarget, 'dragover', function( evt ) { 1353 editable.attachListener( dropTarget, 'dragover', function( evt ) {
1354 // Edge requires this handler to have `preventDefault()` regardless of the situation.
1355 if ( CKEDITOR.env.edge ) {
1356 evt.data.preventDefault();
1357 return;
1358 }
1359
1376 var target = evt.data.getTarget(); 1360 var target = evt.data.getTarget();
1377 1361
1378 // Prevent reloading page when dragging image on empty document (#12619). 1362 // Prevent reloading page when dragging image on empty document (http://dev.ckeditor.com/ticket/12619).
1379 if ( target && target.is && target.is( 'html' ) ) { 1363 if ( target && target.is && target.is( 'html' ) ) {
1380 evt.data.preventDefault(); 1364 evt.data.preventDefault();
1381 return; 1365 return;
@@ -1396,7 +1380,7 @@
1396 // -------------- DROP -------------- 1380 // -------------- DROP --------------
1397 1381
1398 editable.attachListener( dropTarget, 'drop', function( evt ) { 1382 editable.attachListener( dropTarget, 'drop', function( evt ) {
1399 // Do nothing if event was already prevented. (#13879) 1383 // Do nothing if event was already prevented. (http://dev.ckeditor.com/ticket/13879)
1400 if ( evt.data.$.defaultPrevented ) { 1384 if ( evt.data.$.defaultPrevented ) {
1401 return; 1385 return;
1402 } 1386 }
@@ -1407,7 +1391,7 @@
1407 var target = evt.data.getTarget(), 1391 var target = evt.data.getTarget(),
1408 readOnly = target.isReadOnly(); 1392 readOnly = target.isReadOnly();
1409 1393
1410 // Do nothing if drop on non editable element (#13015). 1394 // Do nothing if drop on non editable element (http://dev.ckeditor.com/ticket/13015).
1411 // The <html> tag isn't editable (body is), but we want to allow drop on it 1395 // The <html> tag isn't editable (body is), but we want to allow drop on it
1412 // (so it is possible to drop below editor contents). 1396 // (so it is possible to drop below editor contents).
1413 if ( readOnly && !( target.type == CKEDITOR.NODE_ELEMENT && target.is( 'html' ) ) ) { 1397 if ( readOnly && !( target.type == CKEDITOR.NODE_ELEMENT && target.is( 'html' ) ) ) {
@@ -1582,18 +1566,24 @@
1582 } 1566 }
1583 1567
1584 // In Chrome we can trust Clipboard API, with the exception of Chrome on Android (in both - mobile and desktop modes), where 1568 // In Chrome we can trust Clipboard API, with the exception of Chrome on Android (in both - mobile and desktop modes), where
1585 // clipboard API is not available so we need to check it (#13187). 1569 // clipboard API is not available so we need to check it (http://dev.ckeditor.com/ticket/13187).
1586 if ( CKEDITOR.env.chrome && !dataTransfer.isEmpty() ) { 1570 if ( CKEDITOR.env.chrome && !dataTransfer.isEmpty() ) {
1587 return true; 1571 return true;
1588 } 1572 }
1589 1573
1590 // Because of a Firefox bug HTML data are not available in some cases (e.g. paste from Word), in such cases we 1574 // Because of a Firefox bug HTML data are not available in some cases (e.g. paste from Word), in such cases we
1591 // need to use the pastebin (#13528, https://bugzilla.mozilla.org/show_bug.cgi?id=1183686). 1575 // need to use the pastebin (http://dev.ckeditor.com/ticket/13528, https://bugzilla.mozilla.org/show_bug.cgi?id=1183686).
1592 if ( CKEDITOR.env.gecko && ( dataTransfer.getData( 'text/html' ) || dataTransfer.getFilesCount() ) ) { 1576 if ( CKEDITOR.env.gecko && ( dataTransfer.getData( 'text/html' ) || dataTransfer.getFilesCount() ) ) {
1593 return true; 1577 return true;
1594 } 1578 }
1595 1579
1596 // In Safari and IE HTML data is not available though the Clipboard API. 1580 // Safari fixed clipboard in 10.1 (https://bugs.webkit.org/show_bug.cgi?id=19893) (http://dev.ckeditor.com/ticket/16982).
1581 // However iOS version still doesn't work well enough (https://bugs.webkit.org/show_bug.cgi?id=19893#c34).
1582 if ( CKEDITOR.env.safari && CKEDITOR.env.version >= 603 && !CKEDITOR.env.iOS ) {
1583 return true;
1584 }
1585
1586 // In older Safari and IE HTML data is not available though the Clipboard API.
1597 // In Edge things are a bit messy at the moment - 1587 // In Edge things are a bit messy at the moment -
1598 // https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata 1588 // https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata
1599 // It is safer to use the paste bin in unknown cases. 1589 // It is safer to use the paste bin in unknown cases.
@@ -1610,8 +1600,8 @@
1610 getDropTarget: function( editor ) { 1600 getDropTarget: function( editor ) {
1611 var editable = editor.editable(); 1601 var editable = editor.editable();
1612 1602
1613 // #11123 Firefox needs to listen on document, because otherwise event won't be fired. 1603 // http://dev.ckeditor.com/ticket/11123 Firefox needs to listen on document, because otherwise event won't be fired.
1614 // #11086 IE8 cannot listen on document. 1604 // http://dev.ckeditor.com/ticket/11086 IE8 cannot listen on document.
1615 if ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ) { 1605 if ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ) {
1616 return editable; 1606 return editable;
1617 } else { 1607 } else {
@@ -1809,7 +1799,7 @@
1809 // Check if drop range is inside range. 1799 // Check if drop range is inside range.
1810 // This is an edge case when we drop something on editable's margin/padding. 1800 // This is an edge case when we drop something on editable's margin/padding.
1811 // That space is not treated as a part of the range we drag, so it is possible to drop there. 1801 // That space is not treated as a part of the range we drag, so it is possible to drop there.
1812 // When we drop, browser tries to find closest drop position and it finds it inside drag range. (#13453) 1802 // When we drop, browser tries to find closest drop position and it finds it inside drag range. (http://dev.ckeditor.com/ticket/13453)
1813 var startNode = dragBookmark.startNode, 1803 var startNode = dragBookmark.startNode,
1814 endNode = dragBookmark.endNode, 1804 endNode = dragBookmark.endNode,
1815 dropNode = dropBookmark.startNode, 1805 dropNode = dropBookmark.startNode,
@@ -1863,7 +1853,7 @@
1863 return dropEvt.data.testRange; 1853 return dropEvt.data.testRange;
1864 1854
1865 // Webkits. 1855 // Webkits.
1866 if ( document.caretRangeFromPoint ) { 1856 if ( document.caretRangeFromPoint && editor.document.$.caretRangeFromPoint( x, y ) ) {
1867 $range = editor.document.$.caretRangeFromPoint( x, y ); 1857 $range = editor.document.$.caretRangeFromPoint( x, y );
1868 range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); 1858 range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
1869 range.collapse( true ); 1859 range.collapse( true );
@@ -2089,7 +2079,7 @@
2089 */ 2079 */
2090 initPasteDataTransfer: function( evt, sourceEditor ) { 2080 initPasteDataTransfer: function( evt, sourceEditor ) {
2091 if ( !this.isCustomCopyCutSupported ) { 2081 if ( !this.isCustomCopyCutSupported ) {
2092 // Edge does not support custom copy/cut, but it have some useful data in the clipboardData (#13755). 2082 // Edge does not support custom copy/cut, but it have some useful data in the clipboardData (http://dev.ckeditor.com/ticket/13755).
2093 return new this.dataTransfer( ( CKEDITOR.env.edge && evt && evt.data.$ && evt.data.$.clipboardData ) || null, sourceEditor ); 2083 return new this.dataTransfer( ( CKEDITOR.env.edge && evt && evt.data.$ && evt.data.$.clipboardData ) || null, sourceEditor );
2094 } else if ( evt && evt.data && evt.data.$ ) { 2084 } else if ( evt && evt.data && evt.data.$ ) {
2095 var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor ); 2085 var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor );
@@ -2270,13 +2260,31 @@
2270 * Facade for the native `getData` method. 2260 * Facade for the native `getData` method.
2271 * 2261 *
2272 * @param {String} type The type of data to retrieve. 2262 * @param {String} type The type of data to retrieve.
2263 * @param {Boolean} [getNative=false] Indicates if the whole, original content of the dataTransfer should be returned.
2264 * Introduced in CKEditor 4.7.0.
2273 * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist. 2265 * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist.
2274 */ 2266 */
2275 getData: function( type ) { 2267 getData: function( type, getNative ) {
2276 function isEmpty( data ) { 2268 function isEmpty( data ) {
2277 return data === undefined || data === null || data === ''; 2269 return data === undefined || data === null || data === '';
2278 } 2270 }
2279 2271
2272 function filterUnwantedCharacters( data ) {
2273 if ( typeof data !== 'string' ) {
2274 return data;
2275 }
2276
2277 var htmlEnd = data.indexOf( '</html>' );
2278
2279 if ( htmlEnd !== -1 ) {
2280 // Just cut everything after `</html>`, so everything after htmlEnd index + length of `</html>`.
2281 // Required to workaround bug: https://bugs.chromium.org/p/chromium/issues/detail?id=696978
2282 return data.substring( 0, htmlEnd + 7 );
2283 }
2284
2285 return data;
2286 }
2287
2280 type = this._.normalizeType( type ); 2288 type = this._.normalizeType( type );
2281 2289
2282 var data = this._.data[ type ], 2290 var data = this._.data[ type ],
@@ -2297,8 +2305,9 @@
2297 // This code removes meta tags and returns only the contents of the <body> element if found. Note that 2305 // This code removes meta tags and returns only the contents of the <body> element if found. Note that
2298 // some significant content may be placed outside Start/EndFragment comments so it's kept. 2306 // some significant content may be placed outside Start/EndFragment comments so it's kept.
2299 // 2307 //
2300 // See #13583 for more details. 2308 // See http://dev.ckeditor.com/ticket/13583 for more details.
2301 if ( type == 'text/html' ) { 2309 // Additionally http://dev.ckeditor.com/ticket/16847 adds a flag allowing to get the whole, original content.
2310 if ( type == 'text/html' && !getNative ) {
2302 data = data.replace( this._.metaRegExp, '' ); 2311 data = data.replace( this._.metaRegExp, '' );
2303 2312
2304 // Keep only contents of the <body> element 2313 // Keep only contents of the <body> element
@@ -2318,7 +2327,7 @@
2318 data = ''; 2327 data = '';
2319 } 2328 }
2320 2329
2321 return data; 2330 return filterUnwantedCharacters( data );
2322 }, 2331 },
2323 2332
2324 /** 2333 /**
@@ -2339,7 +2348,7 @@
2339 } 2348 }
2340 2349
2341 // If we use the text type to bind the ID, then if someone tries to set the text, we must also 2350 // If we use the text type to bind the ID, then if someone tries to set the text, we must also
2342 // update ID accordingly. #13468. 2351 // update ID accordingly. http://dev.ckeditor.com/ticket/13468.
2343 if ( clipboardIdDataType == 'Text' && type == 'Text' ) { 2352 if ( clipboardIdDataType == 'Text' && type == 'Text' ) {
2344 this.id = value; 2353 this.id = value;
2345 } 2354 }
@@ -2384,7 +2393,7 @@
2384 function getAndSetData( type ) { 2393 function getAndSetData( type ) {
2385 type = that._.normalizeType( type ); 2394 type = that._.normalizeType( type );
2386 2395
2387 var data = that.getData( type ); 2396 var data = that.getData( type, true );
2388 if ( data ) { 2397 if ( data ) {
2389 that._.data[ type ] = data; 2398 that._.data[ type ] = data;
2390 } 2399 }
@@ -2407,7 +2416,7 @@
2407 if ( ( this.$ && this.$.files ) || file ) { 2416 if ( ( this.$ && this.$.files ) || file ) {
2408 this._.files = []; 2417 this._.files = [];
2409 2418
2410 // Edge have empty files property with no length (#13755). 2419 // Edge have empty files property with no length (http://dev.ckeditor.com/ticket/13755).
2411 if ( this.$.files && this.$.files.length ) { 2420 if ( this.$.files && this.$.files.length ) {
2412 for ( i = 0; i < this.$.files.length; i++ ) { 2421 for ( i = 0; i < this.$.files.length; i++ ) {
2413 this._.files.push( this.$.files[ i ] ); 2422 this._.files.push( this.$.files[ i ] );
@@ -2507,7 +2516,7 @@
2507 2516
2508 /** 2517 /**
2509 * When the content of the clipboard is pasted in Chrome, the clipboard data object has an empty `files` property, 2518 * When the content of the clipboard is pasted in Chrome, the clipboard data object has an empty `files` property,
2510 * but it is possible to get the file as `items[0].getAsFile();` (#12961). 2519 * but it is possible to get the file as `items[0].getAsFile();` (http://dev.ckeditor.com/ticket/12961).
2511 * 2520 *
2512 * @private 2521 * @private
2513 * @returns {File} File instance or `null` if not found. 2522 * @returns {File} File instance or `null` if not found.
@@ -2593,7 +2602,7 @@
2593 * @member CKEDITOR.editor 2602 * @member CKEDITOR.editor
2594 */ 2603 */
2595 2604
2596 /** 2605/**
2597 * Fired after the {@link #paste} event if content was modified. Note that if the paste 2606 * Fired after the {@link #paste} event if content was modified. Note that if the paste
2598 * event does not insert any data, the `afterPaste` event will not be fired. 2607 * event does not insert any data, the `afterPaste` event will not be fired.
2599 * 2608 *
@@ -2602,16 +2611,6 @@
2602 */ 2611 */
2603 2612
2604/** 2613/**
2605 * Internal event to open the Paste dialog window.
2606 *
2607 * @private
2608 * @event pasteDialog
2609 * @member CKEDITOR.editor
2610 * @param {CKEDITOR.editor} editor This editor instance.
2611 * @param {Function} [data] Callback that will be passed to {@link CKEDITOR.editor#openDialog}.
2612 */
2613
2614/**
2615 * Facade for the native `drop` event. Fired when the native `drop` event occurs. 2614 * Facade for the native `drop` event. Fired when the native `drop` event occurs.
2616 * 2615 *
2617 * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event. 2616 * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event.
@@ -2770,3 +2769,12 @@
2770 * @property {CKEDITOR.filter} [pasteFilter] 2769 * @property {CKEDITOR.filter} [pasteFilter]
2771 * @member CKEDITOR.editor 2770 * @member CKEDITOR.editor
2772 */ 2771 */
2772
2773/**
2774 * Duration of the notification displayed after pasting was blocked by the browser.
2775 *
2776 * @since 4.7.0
2777 * @cfg {Number} [clipboard_notificationDuration=10000]
2778 * @member CKEDITOR.config
2779 */
2780CKEDITOR.config.clipboard_notificationDuration = 10000;