2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
8 * File overview: Clipboard support.
12 // COPY & PASTE EXECUTION FLOWS:
14 // * if ( isCustomCopyCutSupported )
15 // * dataTransfer.setData( 'text/html', getSelectedHtml )
17 // * browser's default behavior
19 // * listen onKey (onkeydown)
20 // * fire 'saveSnapshot' on editor
21 // * if ( isCustomCopyCutSupported )
22 // * dataTransfer.setData( 'text/html', getSelectedHtml )
23 // * extractSelectedHtml // remove selected contents
25 // * browser's default behavior
26 // * deferred second 'saveSnapshot' event
28 // * listen onKey (onkeydown)
29 // * simulate 'beforepaste' for non-IEs on editable
30 // * listen 'onpaste' on editable ('onbeforepaste' for IE)
31 // * fire 'beforePaste' on editor
32 // * if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
33 // * fire 'paste' on editor
34 // * !canceled && fire 'afterPaste' on editor
38 // * !success && notification
43 // * !success && notification
45 // * fire 'paste' on editable ('beforepaste' for IE)
46 // * !canceled && execCommand 'paste'
47 // * !success && fire 'pasteDialog' on editor
48 // -- Paste from native context menu & menubar
49 // (Fx & Webkits are handled in 'paste' default listener.
50 // Opera cannot be handled at all because it doesn't fire any events
51 // Special treatment is needed for IE, for which is this part of doc)
53 // * cancel native event
54 // * fire 'beforePaste' on editor
55 // * if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
56 // * execIECommand( 'paste' ) -> this fires another 'paste' event, so cancel it
57 // * fire 'paste' on editor
58 // * !canceled && fire 'afterPaste' on editor
61 // PASTE EVENT - PREPROCESSING:
62 // -- Possible dataValue types: auto, text, html.
63 // -- Possible dataValue contents:
64 // * text (possible \n\r)
65 // * htmlified text (text + br,div,p - no presentational markup & attrs - depends on browser)
68 // * htmlified - if true then content is a HTML even if no markup inside. This flag is set
69 // for content from editable pastebins, because they 'htmlify' pasted content.
72 // * content: htmlified text -> filter, unify text markup (brs, ps, divs), set type: text
73 // * content: html -> filter, set type: html
75 // * content: htmlified text -> filter, unify text markup
76 // * content: html -> filter, strip presentational markup, unify text markup
78 // * content: htmlified text -> filter, unify text markup
79 // * content: html -> filter
82 // * if dataValue is empty copy data from dataTransfer to dataValue (priority 1)
83 // * filtering (priorities 3-5) - e.g. pastefromword filters
84 // * content type sniffing (priority 6)
85 // * markup transformations for text (priority 6)
87 // DRAG & DROP EXECUTION FLOWS:
89 // * save to the global object:
90 // * drag timestamp (with 'cke-' prefix),
94 // * put drag timestamp into event.dataTransfer.text
96 // * if events text == saved timestamp && editor == saved editor
97 // internal drag & drop occurred
98 // * getRangeAtDropPosition
99 // * create bookmarks for drag and drop ranges starting from the end of the document
100 // * dragRange.deleteContents()
101 // * fire 'paste' with saved html and drop range
102 // * if events text == saved timestamp && editor != saved editor
103 // cross editor drag & drop occurred
104 // * getRangeAtDropPosition
105 // * fire 'paste' with saved html
106 // * dragRange.deleteContents()
107 // * FF: refreshCursor on afterPaste
108 // * if events text != saved timestamp
109 // drop form external source occurred
110 // * getRangeAtDropPosition
111 // * if event contains html data then fire 'paste' with html
112 // * else if event contains text data then fire 'paste' with encoded text
113 // * FF: refreshCursor on afterPaste
118 // Register the plugin.
119 CKEDITOR
.plugins
.add( 'clipboard', {
121 // jscs:disable maximumLineLength
122 lang
: 'af,ar,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,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
124 icons
: 'copy,copy-rtl,cut,cut-rtl,paste,paste-rtl', // %REMOVE_LINE_CORE%
125 hidpi
: true, // %REMOVE_LINE_CORE%
126 init: function( editor
) {
128 filtersFactory
= filtersFactoryFactory();
130 if ( editor
.config
.forcePasteAsPlainText
) {
131 filterType
= 'plain-text';
132 } else if ( editor
.config
.pasteFilter
) {
133 filterType
= editor
.config
.pasteFilter
;
135 // On Webkit the pasteFilter defaults 'semantic-content' because pasted data is so terrible
136 // that it must be always filtered.
137 else if ( CKEDITOR
.env
.webkit
&& !( 'pasteFilter' in editor
.config
) ) {
138 filterType
= 'semantic-content';
141 editor
.pasteFilter
= filtersFactory
.get( filterType
);
143 initPasteClipboard( editor
);
144 initDragDrop( editor
);
146 CKEDITOR
.dialog
.add( 'paste', CKEDITOR
.getUrl( this.path
+ 'dialogs/paste.js' ) );
148 editor
.on( 'paste', function( evt
) {
149 // Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
150 if ( !evt
.data
.dataTransfer
) {
151 evt
.data
.dataTransfer
= new CKEDITOR
.plugins
.clipboard
.dataTransfer();
154 // If dataValue is already set (manually or by paste bin), so do not override it.
155 if ( evt
.data
.dataValue
) {
159 var dataTransfer
= evt
.data
.dataTransfer
,
160 // IE support only text data and throws exception if we try to get html data.
161 // This html data object may also be empty if we drag content of the textarea.
162 value
= dataTransfer
.getData( 'text/html' );
165 evt
.data
.dataValue
= value
;
166 evt
.data
.type
= 'html';
168 // Try to get text data otherwise.
169 value
= dataTransfer
.getData( 'text/plain' );
172 evt
.data
.dataValue
= editor
.editable().transformPlainTextToHtml( value
);
173 evt
.data
.type
= 'text';
178 editor
.on( 'paste', function( evt
) {
179 var data
= evt
.data
.dataValue
,
180 blockElements
= CKEDITOR
.dtd
.$block
;
182 // Filter webkit garbage.
183 if ( data
.indexOf( 'Apple-' ) > -1 ) {
184 // Replace special webkit's with simple space, because webkit
185 // produces them even for normal spaces.
186 data
= data
.replace( /<span class="Apple-converted-space"> <\/span>/gi, ' ' );
188 // Strip <span> around white-spaces when not in forced 'html' content type.
189 // This spans are created only when pasting plain text into Webkit,
190 // but for safety reasons remove them always.
191 if ( evt
.data
.type
!= 'html' ) {
192 data
= data
.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi, function( all
, spaces
) {
193 // Replace tabs with 4 spaces like Fx does.
194 return spaces
.replace( /\t/g, ' ' );
198 // This br is produced only when copying & pasting HTML content.
199 if ( data
.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
200 evt
.data
.startsWithEOL
= 1;
201 evt
.data
.preSniffing
= 'html'; // Mark as not text.
202 data
= data
.replace( /<br class="Apple-interchange-newline">/, '' );
205 // Remove all other classes.
206 data
= data
.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
209 // Strip editable that was copied from inside. (#9534)
210 if ( data
.match( /^<[^<]+cke_(editable|contents)/i ) ) {
213 wrapper
= new CKEDITOR
.dom
.element( 'div' );
215 wrapper
.setHtml( data
);
216 // Verify for sure and check for nested editor UI parts. (#9675)
217 while ( wrapper
.getChildCount() == 1 &&
218 ( tmp
= wrapper
.getFirst() ) &&
219 tmp
.type
== CKEDITOR
.NODE_ELEMENT
&& // Make sure first-child is element.
220 ( tmp
.hasClass( 'cke_editable' ) || tmp
.hasClass( 'cke_contents' ) ) ) {
221 wrapper
= editable_wrapper
= tmp
;
224 // If editable wrapper was found strip it and bogus <br> (added on FF).
225 if ( editable_wrapper
)
226 data
= editable_wrapper
.getHtml().replace( /<br>$/i, '' );
229 if ( CKEDITOR
.env
.ie
) {
230 // <p> -> <p> (br.cke-pasted-remove will be removed later)
231 data
= data
.replace( /^ (?: |\r\n)?<(\w+)/g, function( match
, elementName
) {
232 if ( elementName
.toLowerCase() in blockElements
) {
233 evt
.data
.preSniffing
= 'html'; // Mark as not a text.
234 return '<' + elementName
;
238 } else if ( CKEDITOR
.env
.webkit
) {
239 // </p><div><br></div> -> </p><br>
240 // We don't mark br, because this situation can happen for htmlified text too.
241 data
= data
.replace( /<\/(\w+)><div><br><\/div>$/, function( match
, elementName
) {
242 if ( elementName
in blockElements
) {
243 evt
.data
.endsWithEOL
= 1;
244 return '</' + elementName
+ '>';
248 } else if ( CKEDITOR
.env
.gecko
) {
249 // Firefox adds bogus <br> when user pasted text followed by space(s).
250 data
= data
.replace( /(\s)<br>$/, '$1' );
253 evt
.data
.dataValue
= data
;
256 editor
.on( 'paste', function( evt
) {
257 var dataObj
= evt
.data
,
259 data
= dataObj
.dataValue
,
261 // Default is 'html'.
262 defaultType
= editor
.config
.clipboard_defaultContentType
|| 'html',
263 transferType
= dataObj
.dataTransfer
.getTransferType( editor
);
265 // If forced type is 'html' we don't need to know true data type.
266 if ( type
== 'html' || dataObj
.preSniffing
== 'html' ) {
269 trueType
= recogniseContentType( data
);
272 // Unify text markup.
273 if ( trueType
== 'htmlifiedtext' ) {
274 data
= htmlifiedTextHtmlification( editor
.config
, data
);
277 // Strip presentational markup & unify text markup.
278 // Forced plain text (dialog or forcePAPT).
279 // Note: we do not check dontFilter option in this case, because forcePAPT was implemented
280 // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
281 // forcePAPT should have priority as it had before 4.5.
282 if ( type
== 'text' && trueType
== 'html' ) {
283 data
= filterContent( editor
, data
, filtersFactory
.get( 'plain-text' ) );
285 // External paste and pasteFilter exists and filtering isn't disabled.
286 else if ( transferType
== CKEDITOR
.DATA_TRANSFER_EXTERNAL
&& editor
.pasteFilter
&& !dataObj
.dontFilter
) {
287 data
= filterContent( editor
, data
, editor
.pasteFilter
);
290 if ( dataObj
.startsWithEOL
) {
291 data
= '<br data-cke-eol="1">' + data
;
293 if ( dataObj
.endsWithEOL
) {
294 data
+= '<br data-cke-eol="1">';
297 if ( type
== 'auto' ) {
298 type
= ( trueType
== 'html' || defaultType
== 'html' ) ? 'html' : 'text';
302 dataObj
.dataValue
= data
;
303 delete dataObj
.preSniffing
;
304 delete dataObj
.startsWithEOL
;
305 delete dataObj
.endsWithEOL
;
308 // Inserts processed data into the editor at the end of the
310 editor
.on( 'paste', function( evt
) {
313 if ( data
.dataValue
) {
314 editor
.insertHtml( data
.dataValue
, data
.type
, data
.range
);
316 // Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
317 // Fire afterPaste only if paste inserted some HTML.
318 setTimeout( function() {
319 editor
.fire( 'afterPaste' );
322 }, null, null, 1000 );
324 editor
.on( 'pasteDialog', function( evt
) {
325 // TODO it's possible that this setTimeout is not needed any more,
326 // because of changes introduced in the same commit as this comment.
327 // Editor.getClipboardData adds listener to the dialog's events which are
328 // fired after a while (not like 'showDialog').
329 setTimeout( function() {
330 // Open default paste dialog.
331 editor
.openDialog( 'paste', evt
.data
);
337 function firePasteEvents( editor
, data
, withBeforePaste
) {
342 if ( withBeforePaste
) {
343 // Fire 'beforePaste' event so clipboard flavor get customized
345 if ( editor
.fire( 'beforePaste', data
) === false )
346 return false; // Event canceled
349 // Do not fire paste if there is no data (dataValue and dataTranfser are empty).
350 // This check should be done after firing 'beforePaste' because for native paste
351 // 'beforePaste' is by default fired even for empty clipboard.
352 if ( !data
.dataValue
&& data
.dataTransfer
.isEmpty() ) {
356 if ( !data
.dataValue
) {
360 // Because of FF bug we need to use this hack, otherwise cursor is hidden
361 // or it is not possible to move it (#12420).
362 // Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (#13305).
363 if ( CKEDITOR
.env
.gecko
&& data
.method
== 'drop' && editor
.toolbox
) {
364 editor
.once( 'afterPaste', function() {
365 editor
.toolbox
.focus();
369 return editor
.fire( 'paste', data
);
372 function initPasteClipboard( editor
) {
373 var clipboard
= CKEDITOR
.plugins
.clipboard
,
374 preventBeforePasteEvent
= 0,
375 preventPasteEvent
= 0,
379 addButtonsCommands();
382 * Gets clipboard data by directly accessing the clipboard (IE only) or opening the paste dialog window.
384 * editor.getClipboardData( { title: 'Get my data' }, function( data ) {
386 * alert( data.type + ' ' + data.dataValue );
389 * @member CKEDITOR.editor
390 * @param {Object} options
391 * @param {String} [options.title] The title of the paste dialog window.
392 * @param {Function} callback A function that will be executed with `data.type` and `data.dataValue`
393 * or `null` if none of the capturing methods succeeded.
395 editor
.getClipboardData = function( options
, callback
) {
396 var beforePasteNotCanceled
= false,
398 dialogCommited
= false;
400 // Options are optional - args shift.
406 // Listen with maximum priority to handle content before everyone else.
407 // This callback will handle paste event that will be fired if direct
408 // access to the clipboard succeed in IE.
409 editor
.on( 'paste', onPaste
, null, null, 0 );
411 // Listen at the end of listeners chain to see if event wasn't canceled
412 // and to retrieve modified data.type.
413 editor
.on( 'beforePaste', onBeforePaste
, null, null, 1000 );
415 // getClipboardDataDirectly() will fire 'beforePaste' synchronously, so we can
416 // check if it was canceled and if any listener modified data.type.
418 // If command didn't succeed (only IE allows to access clipboard and only if
419 // user agrees) open and handle paste dialog.
420 if ( getClipboardDataDirectly() === false ) {
421 // Direct access to the clipboard wasn't successful so remove listener.
422 editor
.removeListener( 'paste', onPaste
);
424 // If beforePaste was canceled do not open dialog.
425 // Add listeners only if dialog really opened. 'pasteDialog' can be canceled.
426 if ( beforePasteNotCanceled
&& editor
.fire( 'pasteDialog', onDialogOpen
) ) {
427 editor
.on( 'pasteDialogCommit', onDialogCommit
);
429 // 'dialogHide' will be fired after 'pasteDialogCommit'.
430 editor
.on( 'dialogHide', function( evt
) {
431 evt
.removeListener();
432 evt
.data
.removeListener( 'pasteDialogCommit', onDialogCommit
);
434 // Because Opera has to wait a while in pasteDialog we have to wait here.
435 setTimeout( function() {
436 // Notify even if user canceled dialog (clicked 'cancel', ESC, etc).
437 if ( !dialogCommited
)
446 function onPaste( evt
) {
447 evt
.removeListener();
449 callback( evt
.data
);
452 function onBeforePaste( evt
) {
453 evt
.removeListener();
454 beforePasteNotCanceled
= true;
455 dataType
= evt
.data
.type
;
458 function onDialogCommit( evt
) {
459 evt
.removeListener();
460 // Cancel pasteDialogCommit so paste dialog won't automatically fire
461 // 'paste' evt by itself.
463 dialogCommited
= true;
466 dataValue
: evt
.data
.dataValue
,
467 dataTransfer
: evt
.data
.dataTransfer
,
472 function onDialogOpen() {
473 this.customTitle
= ( options
&& options
.title
);
477 function addButtonsCommands() {
478 addButtonCommand( 'Cut', 'cut', createCutCopyCmd( 'cut' ), 10, 1 );
479 addButtonCommand( 'Copy', 'copy', createCutCopyCmd( 'copy' ), 20, 4 );
480 addButtonCommand( 'Paste', 'paste', createPasteCmd(), 30, 8 );
482 function addButtonCommand( buttonName
, commandName
, command
, toolbarOrder
, ctxMenuOrder
) {
483 var lang
= editor
.lang
.clipboard
[ commandName
];
485 editor
.addCommand( commandName
, command
);
486 editor
.ui
.addButton
&& editor
.ui
.addButton( buttonName
, {
488 command
: commandName
,
489 toolbar
: 'clipboard,' + toolbarOrder
492 // If the "menu" plugin is loaded, register the menu item.
493 if ( editor
.addMenuItems
) {
494 editor
.addMenuItem( commandName
, {
496 command
: commandName
,
504 function addListeners() {
505 editor
.on( 'key', onKey
);
506 editor
.on( 'contentDom', addPasteListenersToEditable
);
508 // For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that.
509 editor
.on( 'selectionChange', function( evt
) {
510 inReadOnly
= evt
.data
.selection
.getRanges()[ 0 ].checkReadOnly();
514 // If the "contextmenu" plugin is loaded, register the listeners.
515 if ( editor
.contextMenu
) {
516 editor
.contextMenu
.addListener( function( element
, selection
) {
517 inReadOnly
= selection
.getRanges()[ 0 ].checkReadOnly();
519 cut
: stateFromNamedCommand( 'cut' ),
520 copy
: stateFromNamedCommand( 'copy' ),
521 paste
: stateFromNamedCommand( 'paste' )
527 // Add events listeners to editable.
528 function addPasteListenersToEditable() {
529 var editable
= editor
.editable();
531 if ( CKEDITOR
.plugins
.clipboard
.isCustomCopyCutSupported
) {
532 var initOnCopyCut = function( evt
) {
533 // If user tries to cut in read-only editor, we must prevent default action. (#13872)
534 if ( !editor
.readOnly
|| evt
.name
!= 'cut' ) {
535 clipboard
.initPasteDataTransfer( evt
, editor
);
537 evt
.data
.preventDefault();
540 editable
.on( 'copy', initOnCopyCut
);
541 editable
.on( 'cut', initOnCopyCut
);
543 // Delete content with the low priority so one can overwrite cut data.
544 editable
.on( 'cut', function() {
545 // If user tries to cut in read-only editor, we must prevent default action. (#13872)
546 if ( !editor
.readOnly
) {
547 editor
.extractSelectedHtml();
549 }, null, null, 999 );
552 // We'll be catching all pasted content in one line, regardless of whether
553 // it's introduced by a document command execution (e.g. toolbar buttons) or
554 // user paste behaviors (e.g. CTRL+V).
555 editable
.on( clipboard
.mainPasteEvent
, function( evt
) {
556 if ( clipboard
.mainPasteEvent
== 'beforepaste' && preventBeforePasteEvent
) {
560 // If you've just asked yourself why preventPasteEventNow() is not here, but
561 // in listener for CTRL+V and exec method of 'paste' command
562 // you've asked the same question we did.
566 // First thing to notice - this answer makes sense only for IE,
567 // because other browsers don't listen for 'paste' event.
569 // What would happen if we move preventPasteEventNow() here?
571 // * CTRL+V - IE fires 'beforepaste', so we prevent 'paste' and pasteDataFromClipboard(). OK.
572 // * editor.execCommand( 'paste' ) - we fire 'beforepaste', so we prevent
573 // 'paste' and pasteDataFromClipboard() and doc.execCommand( 'Paste' ). OK.
574 // * native context menu - IE fires 'beforepaste', so we prevent 'paste', but unfortunately
575 // on IE we fail with pasteDataFromClipboard() here, because of... we don't know why, but
576 // we just fail, so... we paste nothing. FAIL.
577 // * native menu bar - the same as for native context menu.
579 // But don't you know any way to distinguish first two cases from last two?
580 // Only one - special flag set in CTRL+V handler and exec method of 'paste'
581 // command. And that's what we did using preventPasteEventNow().
583 pasteDataFromClipboard( evt
);
586 // It's not possible to clearly handle all four paste methods (ctrl+v, native menu bar
587 // native context menu, editor's command) in one 'paste/beforepaste' event in IE.
589 // For ctrl+v & editor's command it's easy to handle pasting in 'beforepaste' listener,
590 // so we do this. For another two methods it's better to use 'paste' event.
592 // 'paste' is always being fired after 'beforepaste' (except of weird one on opening native
593 // context menu), so for two methods handled in 'beforepaste' we're canceling 'paste'
594 // using preventPasteEvent state.
596 // 'paste' event in IE is being fired before getClipboardDataByPastebin executes its callback.
598 // QUESTION: Why didn't you handle all 4 paste methods in handler for 'paste'?
599 // Wouldn't this just be simpler?
600 // ANSWER: Then we would have to evt.data.preventDefault() only for native
601 // context menu and menu bar pastes. The same with execIECommand().
602 // That would force us to mark CTRL+V and editor's paste command with
603 // special flag, other than preventPasteEvent. But we still would have to
604 // have preventPasteEvent for the second event fired by execIECommand.
605 // Code would be longer and not cleaner.
606 if ( clipboard
.mainPasteEvent
== 'beforepaste' ) {
607 editable
.on( 'paste', function( evt
) {
608 if ( preventPasteEvent
) {
612 // Cancel next 'paste' event fired by execIECommand( 'paste' )
613 // at the end of this callback.
614 preventPasteEventNow();
616 // Prevent native paste.
617 evt
.data
.preventDefault();
619 pasteDataFromClipboard( evt
);
621 // Force IE to paste content into pastebin so pasteDataFromClipboard will work.
622 if ( !execIECommand( 'paste' ) ) {
623 editor
.openDialog( 'paste' );
627 // If mainPasteEvent is 'beforePaste' (IE before Edge),
628 // dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953)
629 editable
.on( 'contextmenu', preventBeforePasteEventNow
, null, null, 0 );
631 editable
.on( 'beforepaste', function( evt
) {
632 // Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (#11970).
633 if ( evt
.data
&& !evt
.data
.$.ctrlKey
&& !evt
.data
.$.shiftKey
)
634 preventBeforePasteEventNow();
638 editable
.on( 'beforecut', function() {
639 !preventBeforePasteEvent
&& fixCut( editor
);
644 // Use editor.document instead of editable in non-IEs for observing mouseup
645 // since editable won't fire the event if selection process started within
646 // iframe and ended out of the editor (#9851).
647 editable
.attachListener( CKEDITOR
.env
.ie
? editable
: editor
.document
.getDocumentElement(), 'mouseup', function() {
648 mouseupTimeout
= setTimeout( function() {
653 // Make sure that deferred mouseup callback isn't executed after editor instance
654 // had been destroyed. This may happen when editor.destroy() is called in parallel
655 // with mouseup event (i.e. a button with onclick callback) (#10219).
656 editor
.on( 'destroy', function() {
657 clearTimeout( mouseupTimeout
);
660 editable
.on( 'keyup', setToolbarStates
);
663 // Create object representing Cut or Copy commands.
664 function createCutCopyCmd( type
) {
667 canUndo
: type
== 'cut', // We can't undo copy to clipboard.
670 // Attempts to execute the Cut and Copy operations.
671 function tryToCutCopy( type
) {
672 if ( CKEDITOR
.env
.ie
)
673 return execIECommand( type
);
677 // Other browsers throw an error if the command is disabled.
678 return editor
.document
.$.execCommand( type
, false, null );
684 this.type
== 'cut' && fixCut();
686 var success
= tryToCutCopy( this.type
);
689 // Show cutError or copyError.
690 editor
.showNotification( editor
.lang
.clipboard
[ this.type
+ 'Error' ] ); // jshint ignore:line
698 function createPasteCmd() {
700 // Snapshots are done manually by editable.insertXXX methods.
704 exec: function( editor
, data
) {
706 fire = function( data
, withBeforePaste
) {
707 data
&& firePasteEvents( editor
, data
, !!withBeforePaste
);
709 editor
.fire( 'afterCommandExec', {
716 // Check data precisely - don't open dialog on empty string.
717 if ( typeof data
== 'string' )
721 dataTransfer
: clipboard
.initPasteDataTransfer()
724 editor
.getClipboardData( fire
);
729 function preventPasteEventNow() {
730 preventPasteEvent
= 1;
731 // For safety reason we should wait longer than 0/1ms.
732 // We don't know how long execution of quite complex getClipboardData will take
733 // and in for example 'paste' listener execCommand() (which fires 'paste') is called
734 // after getClipboardData finishes.
735 // Luckily, it's impossible to immediately fire another 'paste' event we want to handle,
736 // because we only handle there native context menu and menu bar.
737 setTimeout( function() {
738 preventPasteEvent
= 0;
742 function preventBeforePasteEventNow() {
743 preventBeforePasteEvent
= 1;
744 setTimeout( function() {
745 preventBeforePasteEvent
= 0;
749 // Tries to execute any of the paste, cut or copy commands in IE. Returns a
750 // boolean indicating that the operation succeeded.
751 // @param {String} command *LOWER CASED* name of command ('paste', 'cut', 'copy').
752 function execIECommand( command
) {
753 var doc
= editor
.document
,
754 body
= doc
.getBody(),
756 onExec = function() {
760 // The following seems to be the only reliable way to detect that
761 // clipboard commands are enabled in IE. It will fire the
762 // onpaste/oncut/oncopy events only if the security settings allowed
763 // the command to execute.
764 body
.on( command
, onExec
);
766 // IE7: document.execCommand has problem to paste into positioned element.
767 if ( CKEDITOR
.env
.version
> 7 ) {
768 doc
.$.execCommand( command
);
770 doc
.$.selection
.createRange().execCommand( command
);
773 body
.removeListener( command
, onExec
);
778 // Cutting off control type element in IE standards breaks the selection entirely. (#4881)
780 if ( !CKEDITOR
.env
.ie
|| CKEDITOR
.env
.quirks
)
783 var sel
= editor
.getSelection(),
784 control
, range
, dummy
;
786 if ( ( sel
.getType() == CKEDITOR
.SELECTION_ELEMENT
) && ( control
= sel
.getSelectedElement() ) ) {
787 range
= sel
.getRanges()[ 0 ];
788 dummy
= editor
.document
.createText( '' );
789 dummy
.insertBefore( control
);
790 range
.setStartBefore( dummy
);
791 range
.setEndAfter( control
);
792 sel
.selectRanges( [ range
] );
794 // Clear up the fix if the paste wasn't succeeded.
795 setTimeout( function() {
796 // Element still online?
797 if ( control
.getParent() ) {
799 sel
.selectElement( control
);
805 // Allow to peek clipboard content by redirecting the
806 // pasting content into a temporary bin and grab the content of it.
807 function getClipboardDataByPastebin( evt
, callback
) {
808 var doc
= editor
.document
,
809 editable
= editor
.editable(),
810 cancel = function( evt
) {
815 // Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
816 if ( doc
.getById( 'cke_pastebin' ) )
819 var sel
= editor
.getSelection();
820 var bms
= sel
.createBookmarks();
822 // #11384. On IE9+ we use native selectionchange (i.e. editor#selectionCheck) to cache the most
823 // recent selection which we then lock on editable blur. See selection.js for more info.
824 // selectionchange fired before getClipboardDataByPastebin() cached selection
825 // before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM),
826 // so we need to fire selectionchange one more time, to store current seleciton.
827 // Selection will be locked when we focus pastebin.
828 if ( CKEDITOR
.env
.ie
)
829 sel
.root
.fire( 'selectionchange' );
831 // Create container to paste into.
832 // For rich content we prefer to use "body" since it holds
833 // the least possibility to be splitted by pasted content, while this may
834 // breaks the text selection on a frame-less editable, "div" would be
835 // the best one in that case.
836 // In another case on old IEs moving the selection into a "body" paste bin causes error panic.
837 // Body can't be also used for Opera which fills it with <br>
838 // what is indistinguishable from pasted <br> (copying <br> in Opera isn't possible,
839 // but it can be copied from other browser).
840 var pastebin
= new CKEDITOR
.dom
.element(
841 ( CKEDITOR
.env
.webkit
|| editable
.is( 'body' ) ) && !CKEDITOR
.env
.ie
? 'body' : 'div', doc
);
843 pastebin
.setAttributes( {
848 var containerOffset
= 0,
850 win
= doc
.getWindow();
852 if ( CKEDITOR
.env
.webkit
) {
853 // It's better to paste close to the real paste destination, so inherited styles
854 // (which Webkits will try to compensate by styling span) differs less from the destination's one.
855 editable
.append( pastebin
);
856 // Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754)
857 pastebin
.addClass( 'cke_editable' );
859 // Compensate position of offsetParent.
860 if ( !editable
.is( 'body' ) ) {
861 // We're not able to get offsetParent from pastebin (body element), so check whether
862 // its parent (editable) is positioned.
863 if ( editable
.getComputedStyle( 'position' ) != 'static' )
864 offsetParent
= editable
;
865 // And if not - safely get offsetParent from editable.
867 offsetParent
= CKEDITOR
.dom
.element
.get( editable
.$.offsetParent
);
869 containerOffset
= offsetParent
.getDocumentPosition().y
;
872 // Opera and IE doesn't allow to append to html element.
873 editable
.getAscendant( CKEDITOR
.env
.ie
? 'body' : 'html', 1 ).append( pastebin
);
876 pastebin
.setStyles( {
877 position
: 'absolute',
878 // Position the bin at the top (+10 for safety) of viewport to avoid any subsequent document scroll.
879 top
: ( win
.getScrollPosition().y
- containerOffset
+ 10 ) + 'px',
881 // Caret has to fit in that height, otherwise browsers like Chrome & Opera will scroll window to show it.
882 // Set height equal to viewport's height - 20px (safety gaps), minimum 1px.
883 height
: Math
.max( 1, win
.getViewPaneSize().height
- 20 ) + 'px',
885 // Reset styles that can mess up pastebin position.
890 // Paste fails in Safari when the body tag has 'user-select: none'. (#12506)
891 if ( CKEDITOR
.env
.safari
)
892 pastebin
.setStyles( CKEDITOR
.tools
.cssVendorPrefix( 'user-select', 'text' ) );
894 // Check if the paste bin now establishes new editing host.
895 var isEditingHost
= pastebin
.getParent().isReadOnly();
897 if ( isEditingHost
) {
898 // Hide the paste bin.
899 pastebin
.setOpacity( 0 );
900 // And make it editable.
901 pastebin
.setAttribute( 'contenteditable', true );
903 // Transparency is not enough since positioned non-editing host always shows
904 // resize handler, pull it off the screen instead.
906 pastebin
.setStyle( editor
.config
.contentsLangDirection
== 'ltr' ? 'left' : 'right', '-10000px' );
909 editor
.on( 'selectionChange', cancel
, null, null, 0 );
911 // Webkit fill fire blur on editable when moving selection to
912 // pastebin (if body is used). Cancel it because it causes incorrect
913 // selection lock in case of inline editor (#10644).
914 // The same seems to apply to Firefox (#10787).
915 if ( CKEDITOR
.env
.webkit
|| CKEDITOR
.env
.gecko
)
916 blurListener
= editable
.once( 'blur', cancel
, null, null, -100 );
918 // Temporarily move selection to the pastebin.
919 isEditingHost
&& pastebin
.focus();
920 var range
= new CKEDITOR
.dom
.range( pastebin
);
921 range
.selectNodeContents( pastebin
);
922 var selPastebin
= range
.select();
924 // If non-native paste is executed, IE will open security alert and blur editable.
925 // Editable will then lock selection inside itself and after accepting security alert
926 // this selection will be restored. We overwrite stored selection, so it's restored
927 // in pastebin. (#9552)
928 if ( CKEDITOR
.env
.ie
) {
929 blurListener
= editable
.once( 'blur', function() {
930 editor
.lockSelection( selPastebin
);
934 var scrollTop
= CKEDITOR
.document
.getWindow().getScrollPosition().y
;
936 // Wait a while and grab the pasted contents.
937 setTimeout( function() {
938 // Restore main window's scroll position which could have been changed
939 // by browser in cases described in #9771.
940 if ( CKEDITOR
.env
.webkit
)
941 CKEDITOR
.document
.getBody().$.scrollTop
= scrollTop
;
943 // Blur will be fired only on non-native paste. In other case manually remove listener.
944 blurListener
&& blurListener
.removeListener();
946 // Restore properly the document focus. (#8849)
947 if ( CKEDITOR
.env
.ie
)
950 // IE7: selection must go before removing pastebin. (#8691)
951 sel
.selectBookmarks( bms
);
954 // Grab the HTML contents.
955 // We need to look for a apple style wrapper on webkit it also adds
956 // a div wrapper if you copy/paste the body of the editor.
957 // Remove hidden div and restore selection.
959 if ( CKEDITOR
.env
.webkit
&& ( bogusSpan
= pastebin
.getFirst() ) && ( bogusSpan
.is
&& bogusSpan
.hasClass( 'Apple-style-span' ) ) )
960 pastebin
= bogusSpan
;
962 editor
.removeListener( 'selectionChange', cancel
);
963 callback( pastebin
.getHtml() );
967 // Try to get content directly on IE from clipboard, without native event
968 // being fired before. In other words - synthetically get clipboard data, if it's possible.
969 // mainPasteEvent will be fired, so if forced native paste:
970 // * worked, getClipboardDataByPastebin will grab it,
971 // * didn't work, dataValue and dataTransfer will be empty and editor#paste won't be fired.
972 // Clipboard data can be accessed directly only on IEs older than Edge.
973 // On other browsers we should fire beforePaste event and return false.
974 function getClipboardDataDirectly() {
975 if ( clipboard
.mainPasteEvent
== 'paste' ) {
976 // beforePaste should be fired when dialog open so it can be canceled.
977 editor
.fire( 'beforePaste', { type
: 'auto', method
: 'paste' } );
981 // Prevent IE from pasting at the begining of the document.
984 // Command will be handled by 'beforepaste', but as
985 // execIECommand( 'paste' ) will fire also 'paste' event
986 // we're canceling it.
987 preventPasteEventNow();
989 // #9247: Lock focus to prevent IE from hiding toolbar for inline editor.
990 var focusManager
= editor
.focusManager
;
993 if ( editor
.editable().fire( clipboard
.mainPasteEvent
) && !execIECommand( 'paste' ) ) {
994 focusManager
.unlock();
997 focusManager
.unlock();
1002 // Listens for some clipboard related keystrokes, so they get customized.
1003 // Needs to be bind to keydown event.
1004 function onKey( event
) {
1005 if ( editor
.mode
!= 'wysiwyg' )
1008 switch ( event
.data
.keyCode
) {
1010 case CKEDITOR
.CTRL
+ 86: // CTRL+V
1011 case CKEDITOR
.SHIFT
+ 45: // SHIFT+INS
1012 var editable
= editor
.editable();
1014 // Cancel 'paste' event because ctrl+v is for IE handled
1015 // by 'beforepaste'.
1016 preventPasteEventNow();
1018 // Simulate 'beforepaste' event for all browsers using 'paste' as main event.
1019 if ( clipboard
.mainPasteEvent
== 'paste' ) {
1020 editable
.fire( 'beforepaste' );
1026 case CKEDITOR
.CTRL
+ 88: // CTRL+X
1027 case CKEDITOR
.SHIFT
+ 46: // SHIFT+DEL
1028 // Save Undo snapshot.
1029 editor
.fire( 'saveSnapshot' ); // Save before cut
1030 setTimeout( function() {
1031 editor
.fire( 'saveSnapshot' ); // Save after cut
1032 }, 50 ); // OSX is slow (#11416).
1036 function pasteDataFromClipboard( evt
) {
1037 // Default type is 'auto', but can be changed by beforePaste listeners.
1041 dataTransfer
: clipboard
.initPasteDataTransfer( evt
)
1044 eventData
.dataTransfer
.cacheData();
1046 // Fire 'beforePaste' event so clipboard flavor get customized by other plugins.
1047 // If 'beforePaste' is canceled continue executing getClipboardDataByPastebin and then do nothing
1048 // (do not fire 'paste', 'afterPaste' events). This way we can grab all - synthetically
1049 // and natively pasted content and prevent its insertion into editor
1050 // after canceling 'beforePaste' event.
1051 var beforePasteNotCanceled
= editor
.fire( 'beforePaste', eventData
) !== false;
1053 // Do not use paste bin if the browser let us get HTML or files from dataTranfer.
1054 if ( beforePasteNotCanceled
&& clipboard
.canClipboardApiBeTrusted( eventData
.dataTransfer
, editor
) ) {
1055 evt
.data
.preventDefault();
1056 setTimeout( function() {
1057 firePasteEvents( editor
, eventData
);
1060 getClipboardDataByPastebin( evt
, function( data
) {
1062 eventData
.dataValue
= data
.replace( /<span[^>]+data-cke-bookmark[^<]*?<\/span>/ig, '' );
1064 // Fire remaining events (without beforePaste)
1065 beforePasteNotCanceled
&& firePasteEvents( editor
, eventData
);
1070 function setToolbarStates() {
1071 if ( editor
.mode
!= 'wysiwyg' )
1074 var pasteState
= stateFromNamedCommand( 'paste' );
1076 editor
.getCommand( 'cut' ).setState( stateFromNamedCommand( 'cut' ) );
1077 editor
.getCommand( 'copy' ).setState( stateFromNamedCommand( 'copy' ) );
1078 editor
.getCommand( 'paste' ).setState( pasteState
);
1079 editor
.fire( 'pasteState', pasteState
);
1082 function stateFromNamedCommand( command
) {
1083 if ( inReadOnly
&& command
in { paste
: 1, cut
: 1 } )
1084 return CKEDITOR
.TRISTATE_DISABLED
;
1086 if ( command
== 'paste' )
1087 return CKEDITOR
.TRISTATE_OFF
;
1089 // Cut, copy - check if the selection is not empty.
1090 var sel
= editor
.getSelection(),
1091 ranges
= sel
.getRanges(),
1092 selectionIsEmpty
= sel
.getType() == CKEDITOR
.SELECTION_NONE
|| ( ranges
.length
== 1 && ranges
[ 0 ].collapsed
);
1094 return selectionIsEmpty
? CKEDITOR
.TRISTATE_DISABLED
: CKEDITOR
.TRISTATE_OFF
;
1099 // * 'htmlifiedtext' if content looks like transformed by browser from plain text.
1100 // See clipboard/paste.html TCs for more info.
1101 // * 'html' if it is not 'htmlifiedtext'.
1102 function recogniseContentType( data
) {
1103 if ( CKEDITOR
.env
.webkit
) {
1104 // Plain text or ( <div><br></div> and text inside <div> ).
1105 if ( !data
.match( /^[^<]*$/g ) && !data
.match( /^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi ) )
1107 } else if ( CKEDITOR
.env
.ie
) {
1108 // Text and <br> or ( text and <br> in <p> - paragraphs can be separated by new \r\n ).
1109 if ( !data
.match( /^([^<]|<br( ?\/)?>)*$/gi ) && !data
.match( /^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi ) )
1111 } else if ( CKEDITOR
.env
.gecko
) {
1113 if ( !data
.match( /^([^<]|<br( ?\/)?>)*$/gi ) )
1119 return 'htmlifiedtext';
1122 // This function transforms what browsers produce when
1123 // pasting plain text into editable element (see clipboard/paste.html TCs
1124 // for more info) into correct HTML (similar to that produced by text2Html).
1125 function htmlifiedTextHtmlification( config
, data
) {
1126 function repeatParagraphs( repeats
) {
1127 // Repeat blocks floor((n+1)/2) times.
1128 // Even number of repeats - add <br> at the beginning of last <p>.
1129 return CKEDITOR
.tools
.repeat( '</p><p>', ~~( repeats
/ 2 ) ) + ( repeats
% 2 == 1 ? '<br>' : '' );
1132 // Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
1133 data
= data
.replace( /\s+/g, ' ' )
1134 // Remove spaces from between tags.
1135 .replace( /> +</g, '><' )
1136 // Normalize XHTML syntax and upper cased <br> tags.
1137 .replace( /<br ?\/>/gi, '<br>' );
1139 // IE - lower cased tags.
1140 data
= data
.replace( /<\/?[A-Z]+>/g, function( match
) {
1141 return match
.toLowerCase();
1144 // Don't touch single lines (no <br|p|div>) - nothing to do here.
1145 if ( data
.match( /^[^<]$/ ) )
1149 if ( CKEDITOR
.env
.webkit
&& data
.indexOf( '<div>' ) > -1 ) {
1150 // One line break at the beginning - insert <br>
1151 data
= data
.replace( /^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>' )
1152 // Two or more - reduce number of new lines by one.
1153 .replace( /^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>' );
1155 // Two line breaks create one paragraph in Webkit.
1156 if ( data
.match( /<div>(<br>|)<\/div>/ ) ) {
1157 data
= '<p>' + data
.replace( /(<div>(<br>|)<\/div>)+/g, function( match
) {
1158 return repeatParagraphs( match
.split( '</div><div>' ).length
+ 1 );
1162 // One line break create br.
1163 data
= data
.replace( /<\/div><div>/g, '<br>' );
1165 // Remove remaining divs.
1166 data
= data
.replace( /<\/?div>/g, '' );
1169 // Opera and Firefox and enterMode != BR.
1170 if ( CKEDITOR
.env
.gecko
&& config
.enterMode
!= CKEDITOR
.ENTER_BR
) {
1171 // Remove bogus <br> - Fx generates two <brs> for one line break.
1172 // For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
1173 if ( CKEDITOR
.env
.gecko
)
1174 data
= data
.replace( /^<br><br>$/, '<br>' );
1176 // This line satisfy edge case when for Opera we have two line breaks
1177 //data = data.replace( /)
1179 if ( data
.indexOf( '<br><br>' ) > -1 ) {
1180 // Two line breaks create one paragraph, three - 2, four - 3, etc.
1181 data
= '<p>' + data
.replace( /(<br>){2,}/g, function( match
) {
1182 return repeatParagraphs( match
.length
/ 4 );
1187 return switchEnterMode( config
, data
);
1190 function filtersFactoryFactory() {
1193 function setUpTags() {
1196 for ( var tag
in CKEDITOR
.dtd
) {
1197 if ( tag
.charAt( 0 ) != '$' && tag
!= 'div' && tag
!= 'span' ) {
1205 function createSemanticContentFilter() {
1206 var filter
= new CKEDITOR
.filter();
1210 elements
: setUpTags(),
1221 get: function( type
) {
1222 if ( type
== 'plain-text' ) {
1223 // Does this look confusing to you? Did we forget about enter mode?
1224 // It is a trick that let's us creating one filter for edidtor, regardless of its
1225 // activeEnterMode (which as the name indicates can change during runtime).
1227 // How does it work?
1228 // The active enter mode is passed to the filter.applyTo method.
1229 // The filter first marks all elements except <br> as disallowed and then tries to remove
1230 // them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
1231 // so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
1233 // Now you can sleep well.
1234 return filters
.plainText
|| ( filters
.plainText
= new CKEDITOR
.filter( 'br' ) );
1235 } else if ( type
== 'semantic-content' ) {
1236 return filters
.semanticContent
|| ( filters
.semanticContent
= createSemanticContentFilter() );
1237 } else if ( type
) {
1238 // Create filter based on rules (string or object).
1239 return new CKEDITOR
.filter( type
);
1247 function filterContent( editor
, data
, filter
) {
1248 var fragment
= CKEDITOR
.htmlParser
.fragment
.fromHtml( data
),
1249 writer
= new CKEDITOR
.htmlParser
.basicWriter();
1251 filter
.applyTo( fragment
, true, false, editor
.activeEnterMode
);
1252 fragment
.writeHtml( writer
);
1254 return writer
.getHtml();
1257 function switchEnterMode( config
, data
) {
1258 if ( config
.enterMode
== CKEDITOR
.ENTER_BR
) {
1259 data
= data
.replace( /(<\/p><p>)+/g, function( match
) {
1260 return CKEDITOR
.tools
.repeat( '<br>', match
.length
/ 7 * 2 );
1261 } ).replace( /<\/?p>/g, '' );
1262 } else if ( config
.enterMode
== CKEDITOR
.ENTER_DIV
) {
1263 data
= data
.replace( /<(\/)?p>/g, '<$1div>' );
1269 function preventDefaultSetDropEffectToNone( evt
) {
1270 evt
.data
.preventDefault();
1271 evt
.data
.$.dataTransfer
.dropEffect
= 'none';
1274 function initDragDrop( editor
) {
1275 var clipboard
= CKEDITOR
.plugins
.clipboard
;
1277 editor
.on( 'contentDom', function() {
1278 var editable
= editor
.editable(),
1279 dropTarget
= CKEDITOR
.plugins
.clipboard
.getDropTarget( editor
),
1280 top
= editor
.ui
.space( 'top' ),
1281 bottom
= editor
.ui
.space( 'bottom' );
1283 // -------------- DRAGOVER TOP & BOTTOM --------------
1285 // Not allowing dragging on toolbar and bottom (#12613).
1286 clipboard
.preventDefaultDropOnElement( top
);
1287 clipboard
.preventDefaultDropOnElement( bottom
);
1289 // -------------- DRAGSTART --------------
1290 // Listed on dragstart to mark internal and cross-editor drag & drop
1291 // and save range and selected HTML.
1293 editable
.attachListener( dropTarget
, 'dragstart', fireDragEvent
);
1295 // Make sure to reset data transfer (in case dragend was not called or was canceled).
1296 editable
.attachListener( editor
, 'dragstart', clipboard
.resetDragDataTransfer
, clipboard
, null, 1 );
1298 // Create a dataTransfer object and save it globally.
1299 editable
.attachListener( editor
, 'dragstart', function( evt
) {
1300 clipboard
.initDragDataTransfer( evt
, editor
);
1303 editable
.attachListener( editor
, 'dragstart', function() {
1304 // Save drag range globally for cross editor D&D.
1305 var dragRange
= clipboard
.dragRange
= editor
.getSelection().getRanges()[ 0 ];
1307 // Store number of children, so we can later tell if any text node was split on drop. (#13011, #13447)
1308 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 10 ) {
1309 clipboard
.dragStartContainerChildCount
= dragRange
? getContainerChildCount( dragRange
.startContainer
) : null;
1310 clipboard
.dragEndContainerChildCount
= dragRange
? getContainerChildCount( dragRange
.endContainer
) : null;
1312 }, null, null, 100 );
1314 // -------------- DRAGEND --------------
1315 // Clean up on dragend.
1317 editable
.attachListener( dropTarget
, 'dragend', fireDragEvent
);
1319 // Init data transfer if someone wants to use it in dragend.
1320 editable
.attachListener( editor
, 'dragend', clipboard
.initDragDataTransfer
, clipboard
, null, 1 );
1322 // When drag & drop is done we need to reset dataTransfer so the future
1323 // external drop will be not recognize as internal.
1324 editable
.attachListener( editor
, 'dragend', clipboard
.resetDragDataTransfer
, clipboard
, null, 100 );
1326 // -------------- DRAGOVER --------------
1327 // We need to call preventDefault on dragover because otherwise if
1328 // we drop image it will overwrite document.
1330 editable
.attachListener( dropTarget
, 'dragover', function( evt
) {
1331 var target
= evt
.data
.getTarget();
1333 // Prevent reloading page when dragging image on empty document (#12619).
1334 if ( target
&& target
.is
&& target
.is( 'html' ) ) {
1335 evt
.data
.preventDefault();
1339 // If we do not prevent default dragover on IE the file path
1340 // will be loaded and we will lose content. On the other hand
1341 // if we prevent it the cursor will not we shown, so we prevent
1342 // dragover only on IE, on versions which support file API and only
1343 // if the event contains files.
1344 if ( CKEDITOR
.env
.ie
&&
1345 CKEDITOR
.plugins
.clipboard
.isFileApiSupported
&&
1346 evt
.data
.$.dataTransfer
.types
.contains( 'Files' ) ) {
1347 evt
.data
.preventDefault();
1351 // -------------- DROP --------------
1353 editable
.attachListener( dropTarget
, 'drop', function( evt
) {
1354 // Do nothing if event was already prevented. (#13879)
1355 if ( evt
.data
.$.defaultPrevented
) {
1359 // Cancel native drop.
1360 evt
.data
.preventDefault();
1362 var target
= evt
.data
.getTarget(),
1363 readOnly
= target
.isReadOnly();
1365 // Do nothing if drop on non editable element (#13015).
1366 // The <html> tag isn't editable (body is), but we want to allow drop on it
1367 // (so it is possible to drop below editor contents).
1368 if ( readOnly
&& !( target
.type
== CKEDITOR
.NODE_ELEMENT
&& target
.is( 'html' ) ) ) {
1372 // Getting drop position is one of the most complex parts.
1373 var dropRange
= clipboard
.getRangeAtDropPosition( evt
, editor
),
1374 dragRange
= clipboard
.dragRange
;
1376 // Do nothing if it was not possible to get drop range.
1382 fireDragEvent( evt
, dragRange
, dropRange
);
1383 }, null, null, 9999 );
1385 // Create dataTransfer or get it, if it was created before.
1386 editable
.attachListener( editor
, 'drop', clipboard
.initDragDataTransfer
, clipboard
, null, 1 );
1388 // Execute drop action, fire paste.
1389 editable
.attachListener( editor
, 'drop', function( evt
) {
1390 var data
= evt
.data
;
1396 // Let user modify drag and drop range.
1397 var dropRange
= data
.dropRange
,
1398 dragRange
= data
.dragRange
,
1399 dataTransfer
= data
.dataTransfer
;
1401 if ( dataTransfer
.getTransferType( editor
) == CKEDITOR
.DATA_TRANSFER_INTERNAL
) {
1402 // Execute drop with a timeout because otherwise selection, after drop,
1403 // on IE is in the drag position, instead of drop position.
1404 setTimeout( function() {
1405 clipboard
.internalDrop( dragRange
, dropRange
, dataTransfer
, editor
);
1407 } else if ( dataTransfer
.getTransferType( editor
) == CKEDITOR
.DATA_TRANSFER_CROSS_EDITORS
) {
1408 crossEditorDrop( dragRange
, dropRange
, dataTransfer
);
1410 externalDrop( dropRange
, dataTransfer
);
1412 }, null, null, 9999 );
1414 // Cross editor drag and drop (drag in one Editor and drop in the other).
1415 function crossEditorDrop( dragRange
, dropRange
, dataTransfer
) {
1416 // Paste event should be fired before delete contents because otherwise
1417 // Chrome have a problem with drop range (Chrome split the drop
1418 // range container so the offset is bigger then container length).
1420 firePasteEvents( editor
, { dataTransfer
: dataTransfer
, method
: 'drop' }, 1 );
1422 // Remove dragged content and make a snapshot.
1423 dataTransfer
.sourceEditor
.fire( 'saveSnapshot' );
1425 dataTransfer
.sourceEditor
.editable().extractHtmlFromRange( dragRange
);
1427 // Make some selection before saving snapshot, otherwise error will be thrown, because
1428 // there will be no valid selection after content is removed.
1429 dataTransfer
.sourceEditor
.getSelection().selectRanges( [ dragRange
] );
1430 dataTransfer
.sourceEditor
.fire( 'saveSnapshot' );
1433 // Drop from external source.
1434 function externalDrop( dropRange
, dataTransfer
) {
1435 // Paste content into the drop position.
1438 firePasteEvents( editor
, { dataTransfer
: dataTransfer
, method
: 'drop' }, 1 );
1440 // Usually we reset DataTranfer on dragend,
1441 // but dragend is called on the same element as dragstart
1442 // so it will not be called on on external drop.
1443 clipboard
.resetDragDataTransfer();
1446 // Fire drag/drop events (dragstart, dragend, drop).
1447 function fireDragEvent( evt
, dragRange
, dropRange
) {
1450 target
: evt
.data
.getTarget()
1454 eventData
.dragRange
= dragRange
;
1457 eventData
.dropRange
= dropRange
;
1460 if ( editor
.fire( evt
.name
, eventData
) === false ) {
1461 evt
.data
.preventDefault();
1465 function getContainerChildCount( container
) {
1466 if ( container
.type
!= CKEDITOR
.NODE_ELEMENT
) {
1467 container
= container
.getParent();
1470 return container
.getChildCount();
1477 * @class CKEDITOR.plugins.clipboard
1479 CKEDITOR
.plugins
.clipboard
= {
1481 * True if the environment allows to set data on copy or cut manually. This value is false in IE, because this browser
1482 * shows the security dialog window when the script tries to set clipboard data and on iOS, because custom data is
1483 * not saved to clipboard there.
1487 * @property {Boolean}
1489 isCustomCopyCutSupported
: !CKEDITOR
.env
.ie
&& !CKEDITOR
.env
.iOS
,
1492 * True if the environment supports MIME types and custom data types in dataTransfer/cliboardData getData/setData methods.
1496 * @property {Boolean}
1498 isCustomDataTypesSupported
: !CKEDITOR
.env
.ie
,
1501 * True if the environment supports File API.
1505 * @property {Boolean}
1507 isFileApiSupported
: !CKEDITOR
.env
.ie
|| CKEDITOR
.env
.version
> 9,
1510 * Main native paste event editable should listen to.
1512 * **Note:** Safari does not like the {@link CKEDITOR.editor#beforePaste} event — it sometimes does not
1513 * handle <kbd>Ctrl+C</kbd> properly. This is probably caused by some race condition between events.
1514 * Chrome, Firefox and Edge work well with both events, so it is better to use {@link CKEDITOR.editor#paste}
1515 * which will handle pasting from e.g. browsers' menu bars.
1516 * IE7/8 does not like the {@link CKEDITOR.editor#paste} event for which it is throwing random errors.
1520 * @property {String}
1522 mainPasteEvent
: ( CKEDITOR
.env
.ie
&& !CKEDITOR
.env
.edge
) ? 'beforepaste' : 'paste',
1525 * Returns `true` if it is expected that a browser provides HTML data through the Clipboard API.
1526 * If not, this method returns `false` and as a result CKEditor will use the paste bin. Read more in
1527 * the [Clipboard Integration](http://docs.ckeditor.com/#!/guide/dev_clipboard-section-clipboard-api) guide.
1530 * @returns {Boolean}
1532 canClipboardApiBeTrusted: function( dataTransfer
, editor
) {
1533 // If it's an internal or cross-editor data transfer, then it means that custom cut/copy/paste support works
1534 // and that the data were put manually on the data transfer so we can be sure that it's available.
1535 if ( dataTransfer
.getTransferType( editor
) != CKEDITOR
.DATA_TRANSFER_EXTERNAL
) {
1539 // In Chrome we can trust Clipboard API, with the exception of Chrome on Android (in both - mobile and desktop modes), where
1540 // clipboard API is not available so we need to check it (#13187).
1541 if ( CKEDITOR
.env
.chrome
&& !dataTransfer
.isEmpty() ) {
1545 // Because of a Firefox bug HTML data are not available in some cases (e.g. paste from Word), in such cases we
1546 // need to use the pastebin (#13528, https://bugzilla.mozilla.org/show_bug.cgi?id=1183686).
1547 if ( CKEDITOR
.env
.gecko
&& ( dataTransfer
.getData( 'text/html' ) || dataTransfer
.getFilesCount() ) ) {
1551 // In Safari and IE HTML data is not available though the Clipboard API.
1552 // In Edge things are a bit messy at the moment -
1553 // https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata
1554 // It is safer to use the paste bin in unknown cases.
1559 * Returns the element that should be used as the target for the drop event.
1562 * @param {CKEDITOR.editor} editor The editor instance.
1563 * @returns {CKEDITOR.dom.domObject} the element that should be used as the target for the drop event.
1565 getDropTarget: function( editor
) {
1566 var editable
= editor
.editable();
1568 // #11123 Firefox needs to listen on document, because otherwise event won't be fired.
1569 // #11086 IE8 cannot listen on document.
1570 if ( ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 9 ) || editable
.isInline() ) {
1573 return editor
.document
;
1578 * IE 8 & 9 split text node on drop so the first node contains the
1579 * text before the drop position and the second contains the rest. If you
1580 * drag the content from the same node you will be not be able to get
1581 * it (the range becomes invalid), so you need to join them back.
1583 * Note that the first node in IE 8 & 9 is the original node object
1584 * but with shortened content.
1587 * --- Text Node A ----------------------------------
1592 * --- Text Node A ----- --- Text Node B -----------
1594 * Drop position Drag position
1597 * After (other browsers):
1598 * --- Text Node A ----------------------------------
1600 * Drop position Drag position
1602 * **Note:** This function is in the public scope for tests usage only.
1606 * @param {CKEDITOR.dom.range} dragRange The drag range.
1607 * @param {CKEDITOR.dom.range} dropRange The drop range.
1608 * @param {Number} preDragStartContainerChildCount The number of children of the drag range start container before the drop.
1609 * @param {Number} preDragEndContainerChildCount The number of children of the drag range end container before the drop.
1611 fixSplitNodesAfterDrop: function( dragRange
, dropRange
, preDragStartContainerChildCount
, preDragEndContainerChildCount
) {
1612 var dropContainer
= dropRange
.startContainer
;
1615 typeof preDragEndContainerChildCount
!= 'number' ||
1616 typeof preDragStartContainerChildCount
!= 'number'
1621 // We are only concerned about ranges anchored in elements.
1622 if ( dropContainer
.type
!= CKEDITOR
.NODE_ELEMENT
) {
1626 if ( handleContainer( dragRange
.startContainer
, dropContainer
, preDragStartContainerChildCount
) ) {
1630 if ( handleContainer( dragRange
.endContainer
, dropContainer
, preDragEndContainerChildCount
) ) {
1634 function handleContainer( dragContainer
, dropContainer
, preChildCount
) {
1635 var dragElement
= dragContainer
;
1636 if ( dragElement
.type
== CKEDITOR
.NODE_TEXT
) {
1637 dragElement
= dragContainer
.getParent();
1640 if ( dragElement
.equals( dropContainer
) && preChildCount
!= dropContainer
.getChildCount() ) {
1641 applyFix( dropRange
);
1646 function applyFix( dropRange
) {
1647 var nodeBefore
= dropRange
.startContainer
.getChild( dropRange
.startOffset
- 1 ),
1648 nodeAfter
= dropRange
.startContainer
.getChild( dropRange
.startOffset
);
1651 nodeBefore
&& nodeBefore
.type
== CKEDITOR
.NODE_TEXT
&&
1652 nodeAfter
&& nodeAfter
.type
== CKEDITOR
.NODE_TEXT
1654 var offset
= nodeBefore
.getLength();
1656 nodeBefore
.setText( nodeBefore
.getText() + nodeAfter
.getText() );
1659 dropRange
.setStart( nodeBefore
, offset
);
1660 dropRange
.collapse( true );
1666 * Checks whether turning the drag range into bookmarks will invalidate the drop range.
1667 * This usually happens when the drop range shares the container with the drag range and is
1668 * located after the drag range, but there are countless edge cases.
1670 * This function is stricly related to {@link #internalDrop} which toggles
1671 * order in which it creates bookmarks for both ranges based on a value returned
1672 * by this method. In some cases this method returns a value which is not necessarily
1673 * true in terms of what it was meant to check, but it is convenient, because
1674 * we know how it is interpreted in {@link #internalDrop}, so the correct
1675 * behavior of the entire algorithm is assured.
1677 * **Note:** This function is in the public scope for tests usage only.
1681 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
1682 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
1683 * @returns {Boolean} `true` if the first range is before the second range.
1685 isDropRangeAffectedByDragRange: function( dragRange
, dropRange
) {
1686 var dropContainer
= dropRange
.startContainer
,
1687 dropOffset
= dropRange
.endOffset
;
1689 // Both containers are the same and drop offset is at the same position or later.
1690 // " A L] A " " M A "
1692 if ( dragRange
.endContainer
.equals( dropContainer
) && dragRange
.endOffset
<= dropOffset
) {
1696 // Bookmark for drag start container will mess up with offsets.
1697 // " O [L A " " M A "
1700 dragRange
.startContainer
.getParent().equals( dropContainer
) &&
1701 dragRange
.startContainer
.getIndex() < dropOffset
1706 // Bookmark for drag end container will mess up with offsets.
1707 // " O] L A " " M A "
1710 dragRange
.endContainer
.getParent().equals( dropContainer
) &&
1711 dragRange
.endContainer
.getIndex() < dropOffset
1720 * Internal drag and drop (drag and drop in the same editor instance).
1722 * **Note:** This function is in the public scope for tests usage only.
1726 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
1727 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
1728 * @param {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer
1729 * @param {CKEDITOR.editor} editor
1731 internalDrop: function( dragRange
, dropRange
, dataTransfer
, editor
) {
1732 var clipboard
= CKEDITOR
.plugins
.clipboard
,
1733 editable
= editor
.editable(),
1734 dragBookmark
, dropBookmark
, isDropRangeAffected
;
1736 // Save and lock snapshot so there will be only
1737 // one snapshot for both remove and insert content.
1738 editor
.fire( 'saveSnapshot' );
1739 editor
.fire( 'lockSnapshot', { dontUpdate
: 1 } );
1741 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 10 ) {
1742 this.fixSplitNodesAfterDrop(
1745 clipboard
.dragStartContainerChildCount
,
1746 clipboard
.dragEndContainerChildCount
1750 // Because we manipulate multiple ranges we need to do it carefully,
1751 // changing one range (event creating a bookmark) may make other invalid.
1752 // We need to change ranges into bookmarks so we can manipulate them easily in the future.
1753 // We can change the range which is later in the text before we change the preceding range.
1754 // We call isDropRangeAffectedByDragRange to test the order of ranges.
1755 isDropRangeAffected
= this.isDropRangeAffectedByDragRange( dragRange
, dropRange
);
1756 if ( !isDropRangeAffected
) {
1757 dragBookmark
= dragRange
.createBookmark( false );
1759 dropBookmark
= dropRange
.clone().createBookmark( false );
1760 if ( isDropRangeAffected
) {
1761 dragBookmark
= dragRange
.createBookmark( false );
1764 // Check if drop range is inside range.
1765 // This is an edge case when we drop something on editable's margin/padding.
1766 // That space is not treated as a part of the range we drag, so it is possible to drop there.
1767 // When we drop, browser tries to find closest drop position and it finds it inside drag range. (#13453)
1768 var startNode
= dragBookmark
.startNode
,
1769 endNode
= dragBookmark
.endNode
,
1770 dropNode
= dropBookmark
.startNode
,
1771 dropInsideDragRange
=
1772 // Must check endNode because dragRange could be collapsed in some edge cases (simulated DnD).
1774 ( startNode
.getPosition( dropNode
) & CKEDITOR
.POSITION_PRECEDING
) &&
1775 ( endNode
.getPosition( dropNode
) & CKEDITOR
.POSITION_FOLLOWING
);
1777 // If the drop range happens to be inside drag range change it's position to the beginning of the drag range.
1778 if ( dropInsideDragRange
) {
1779 // We only change position of bookmark span that is connected with dropBookmark.
1780 // dropRange will be overwritten and set to the dropBookmark later.
1781 dropNode
.insertBefore( startNode
);
1784 // No we can safely delete content for the drag range...
1785 dragRange
= editor
.createRange();
1786 dragRange
.moveToBookmark( dragBookmark
);
1787 editable
.extractHtmlFromRange( dragRange
, 1 );
1789 // ...and paste content into the drop position.
1790 dropRange
= editor
.createRange();
1791 dropRange
.moveToBookmark( dropBookmark
);
1793 // We do not select drop range, because of may be in the place we can not set the selection
1794 // (e.g. between blocks, in case of block widget D&D). We put range to the paste event instead.
1795 firePasteEvents( editor
, { dataTransfer
: dataTransfer
, method
: 'drop', range
: dropRange
}, 1 );
1797 editor
.fire( 'unlockSnapshot' );
1801 * Gets the range from the `drop` event.
1804 * @param {Object} domEvent A native DOM drop event object.
1805 * @param {CKEDITOR.editor} editor The source editor instance.
1806 * @returns {CKEDITOR.dom.range} range at drop position.
1808 getRangeAtDropPosition: function( dropEvt
, editor
) {
1809 var $evt
= dropEvt
.data
.$,
1813 defaultRange
= editor
.getSelection( true ).getRanges()[ 0 ],
1814 range
= editor
.createRange();
1816 // Make testing possible.
1817 if ( dropEvt
.data
.testRange
)
1818 return dropEvt
.data
.testRange
;
1821 if ( document
.caretRangeFromPoint
) {
1822 $range
= editor
.document
.$.caretRangeFromPoint( x
, y
);
1823 range
.setStart( CKEDITOR
.dom
.node( $range
.startContainer
), $range
.startOffset
);
1824 range
.collapse( true );
1827 else if ( $evt
.rangeParent
) {
1828 range
.setStart( CKEDITOR
.dom
.node( $evt
.rangeParent
), $evt
.rangeOffset
);
1829 range
.collapse( true );
1832 // We check if editable is focused to make sure that it's an internal DnD. External DnD must use the second
1833 // mechanism because of http://dev.ckeditor.com/ticket/13472#comment:6.
1834 else if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
> 8 && defaultRange
&& editor
.editable().hasFocus
) {
1835 // On IE 9+ range by default is where we expected it.
1836 // defaultRange may be undefined if dragover was canceled (file drop).
1837 return defaultRange
;
1839 // IE 8 and all IEs if !defaultRange or external DnD.
1840 else if ( document
.body
.createTextRange
) {
1841 // To use this method we need a focus (which may be somewhere else in case of external drop).
1844 $range
= editor
.document
.getBody().$.createTextRange();
1848 // If user drop between text line IEs moveToPoint throws exception:
1850 // Lorem ipsum pulvinar purus et euismod
1852 // dolor sit amet,| consectetur adipiscing
1854 // vestibulum tincidunt augue eget tempus.
1856 // * - drop position
1857 // | - expected cursor position
1859 // So we try to call moveToPoint with +-1px up to +-20px above or
1860 // below original drop position to find nearest good drop position.
1861 for ( var i
= 0; i
< 20 && !sucess
; i
++ ) {
1864 $range
.moveToPoint( x
, y
- i
);
1871 $range
.moveToPoint( x
, y
+ i
);
1879 var id
= 'cke-temp-' + ( new Date() ).getTime();
1880 $range
.pasteHTML( '<span id="' + id
+ '">\u200b</span>' );
1882 var span
= editor
.document
.getById( id
);
1883 range
.moveToPosition( span
, CKEDITOR
.POSITION_BEFORE_START
);
1886 // If the fist method does not succeed we might be next to
1887 // the short element (like header):
1889 // Lorem ipsum pulvinar purus et euismod.
1895 // vestibulum tincidunt augue eget tempus.
1897 // * - drop position
1898 // | - expected cursor position
1900 // In such situation elementFromPoint returns proper element. Using getClientRect
1901 // it is possible to check if the cursor should be at the beginning or at the end
1903 var $element
= editor
.document
.$.elementFromPoint( x
, y
),
1904 element
= new CKEDITOR
.dom
.element( $element
),
1907 if ( !element
.equals( editor
.editable() ) && element
.getName() != 'html' ) {
1908 rect
= element
.getClientRect();
1910 if ( x
< rect
.left
) {
1911 range
.setStartAt( element
, CKEDITOR
.POSITION_AFTER_START
);
1912 range
.collapse( true );
1914 range
.setStartAt( element
, CKEDITOR
.POSITION_BEFORE_END
);
1915 range
.collapse( true );
1918 // If drop happens on no element elementFromPoint returns html or body.
1920 // * |Lorem ipsum pulvinar purus et euismod.
1922 // vestibulum tincidunt augue eget tempus.
1924 // * - drop position
1925 // | - expected cursor position
1927 // In such case we can try to use default selection. If startContainer is not
1928 // 'editable' element it is probably proper selection.
1929 else if ( defaultRange
&& defaultRange
.startContainer
&&
1930 !defaultRange
.startContainer
.equals( editor
.editable() ) ) {
1931 return defaultRange
;
1933 // Otherwise we can not find any drop position and we have to return null
1934 // and cancel drop event.
1951 * This function tries to link the `evt.data.dataTransfer` property of the {@link CKEDITOR.editor#dragstart},
1952 * {@link CKEDITOR.editor#dragend} and {@link CKEDITOR.editor#drop} events to a single
1953 * {@link CKEDITOR.plugins.clipboard.dataTransfer} object.
1955 * This method is automatically used by the core of the drag and drop functionality and
1956 * usually does not have to be called manually when using the drag and drop events.
1958 * This method behaves differently depending on whether the drag and drop events were fired
1959 * artificially (to represent a non-native drag and drop) or whether they were caused by the native drag and drop.
1961 * If the native event is not available, then it will create a new {@link CKEDITOR.plugins.clipboard.dataTransfer}
1962 * instance (if it does not exist already) and will link it to this and all following event objects until
1963 * the {@link #resetDragDataTransfer} method is called. It means that all three drag and drop events must be fired
1964 * in order to ensure that the data transfer is bound correctly.
1966 * If the native event is available, then the {@link CKEDITOR.plugins.clipboard.dataTransfer} is identified
1967 * by its ID and a new instance is assigned to the `evt.data.dataTransfer` only if the ID changed or
1968 * the {@link #resetDragDataTransfer} method was called.
1971 * @param {CKEDITOR.dom.event} [evt] A drop event object.
1972 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
1974 initDragDataTransfer: function( evt
, sourceEditor
) {
1975 // Create a new dataTransfer object based on the drop event.
1976 // If this event was used on dragstart to create dataTransfer
1977 // both dataTransfer objects will have the same id.
1978 var nativeDataTransfer
= evt
.data
.$ ? evt
.data
.$.dataTransfer
: null,
1979 dataTransfer
= new this.dataTransfer( nativeDataTransfer
, sourceEditor
);
1981 if ( !nativeDataTransfer
) {
1983 if ( this.dragData
) {
1984 dataTransfer
= this.dragData
;
1986 this.dragData
= dataTransfer
;
1989 // Native event. If there is the same id we will replace dataTransfer with the one
1990 // created on drag, because it contains drag editor, drag content and so on.
1991 // Otherwise (in case of drag from external source) we save new object to
1992 // the global clipboard.dragData.
1993 if ( this.dragData
&& dataTransfer
.id
== this.dragData
.id
) {
1994 dataTransfer
= this.dragData
;
1996 this.dragData
= dataTransfer
;
2000 evt
.data
.dataTransfer
= dataTransfer
;
2004 * Removes the global {@link #dragData} so the next call to {@link #initDragDataTransfer}
2005 * always creates a new instance of {@link CKEDITOR.plugins.clipboard.dataTransfer}.
2009 resetDragDataTransfer: function() {
2010 this.dragData
= null;
2014 * Global object storing the data transfer of the current drag and drop operation.
2015 * Do not use it directly, use {@link #initDragDataTransfer} and {@link #resetDragDataTransfer}.
2017 * Note: This object is global (meaning that it is not related to a single editor instance)
2018 * in order to handle drag and drop from one editor into another.
2022 * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData
2026 * Range object to save the drag range and remove its content after the drop.
2030 * @property {CKEDITOR.dom.range} dragRange
2034 * Initializes and links data transfer objects based on the paste event. If the data
2035 * transfer object was already initialized on this event, the function will
2036 * return that object. In IE it is not possible to link copy/cut and paste events
2037 * so the method always returns a new object. The same happens if there is no paste event
2038 * passed to the method.
2041 * @param {CKEDITOR.dom.event} [evt] A paste event object.
2042 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
2043 * @returns {CKEDITOR.plugins.clipboard.dataTransfer} The data transfer object.
2045 initPasteDataTransfer: function( evt
, sourceEditor
) {
2046 if ( !this.isCustomCopyCutSupported
) {
2047 // Edge does not support custom copy/cut, but it have some useful data in the clipboardData (#13755).
2048 return new this.dataTransfer( ( CKEDITOR
.env
.edge
&& evt
&& evt
.data
.$ && evt
.data
.$.clipboardData
) || null, sourceEditor
);
2049 } else if ( evt
&& evt
.data
&& evt
.data
.$ ) {
2050 var dataTransfer
= new this.dataTransfer( evt
.data
.$.clipboardData
, sourceEditor
);
2052 if ( this.copyCutData
&& dataTransfer
.id
== this.copyCutData
.id
) {
2053 dataTransfer
= this.copyCutData
;
2054 dataTransfer
.$ = evt
.data
.$.clipboardData
;
2056 this.copyCutData
= dataTransfer
;
2059 return dataTransfer
;
2061 return new this.dataTransfer( null, sourceEditor
);
2066 * Prevents dropping on the specified element.
2069 * @param {CKEDITOR.dom.element} element The element on which dropping should be disabled.
2071 preventDefaultDropOnElement: function( element
) {
2072 element
&& element
.on( 'dragover', preventDefaultSetDropEffectToNone
);
2076 // Data type used to link drag and drop events.
2078 // In IE URL data type is buggie and there is no way to mark drag & drop without
2079 // modifying text data (which would be displayed if user drop content to the textarea)
2080 // so we just read dragged text.
2082 // In Chrome and Firefox we can use custom data types.
2083 var clipboardIdDataType
= CKEDITOR
.plugins
.clipboard
.isCustomDataTypesSupported
? 'cke/id' : 'Text';
2085 * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences
2089 * @class CKEDITOR.plugins.clipboard.dataTransfer
2090 * @constructor Creates a class instance.
2091 * @param {Object} [nativeDataTransfer] A native data transfer object.
2092 * @param {CKEDITOR.editor} [editor] The source editor instance. If the editor is defined, dataValue will
2093 * be created based on the editor content and the type will be 'html'.
2095 CKEDITOR
.plugins
.clipboard
.dataTransfer = function( nativeDataTransfer
, editor
) {
2096 if ( nativeDataTransfer
) {
2097 this.$ = nativeDataTransfer
;
2101 metaRegExp
: /^<meta.*?>/i,
2102 bodyRegExp
: /<body(?:[\s\S]*?)>([\s\S]*)<\/body>/i,
2103 fragmentRegExp
: /<!--(?:Start|End)Fragment-->/g,
2108 normalizeType: function( type
) {
2109 type
= type
.toLowerCase();
2111 if ( type
== 'text' || type
== 'text/plain' ) {
2112 return 'Text'; // IE support only Text and URL;
2113 } else if ( type
== 'url' ) {
2114 return 'URL'; // IE support only Text and URL;
2121 // Check if ID is already created.
2122 this.id
= this.getData( clipboardIdDataType
);
2124 // If there is no ID we need to create it. Different browsers needs different ID.
2126 if ( clipboardIdDataType
== 'Text' ) {
2127 // For IE10+ only Text data type is supported and we have to compare dragged
2128 // and dropped text. If the ID is not set it means that empty string was dragged
2129 // (ex. image with no alt). We change null to empty string.
2132 // String for custom data type.
2133 this.id
= 'cke-' + CKEDITOR
.tools
.getUniqueId();
2137 // In IE10+ we can not use any data type besides text, so we do not call setData.
2138 if ( clipboardIdDataType
!= 'Text' ) {
2139 // Try to set ID so it will be passed from the drag to the drop event.
2140 // On some browsers with some event it is not possible to setData so we
2141 // need to catch exceptions.
2143 this.$.setData( clipboardIdDataType
, this.id
);
2148 this.sourceEditor
= editor
;
2150 this.setData( 'text/html', editor
.getSelectedHtml( 1 ) );
2152 // Without setData( 'text', ... ) on dragstart there is no drop event in Safari.
2153 // Also 'text' data is empty as drop to the textarea does not work if we do not put there text.
2154 if ( clipboardIdDataType
!= 'Text' && !this.getData( 'text/plain' ) ) {
2155 this.setData( 'text/plain', editor
.getSelection().getSelectedText() );
2160 * Data transfer ID used to bind all dataTransfer
2161 * objects based on the same event (e.g. in drag and drop events).
2164 * @property {String} id
2168 * A native DOM event object.
2171 * @property {Object} $
2175 * Source editor — the editor where the drag starts.
2176 * Might be undefined if the drag starts outside the editor (e.g. when dropping files to the editor).
2179 * @property {CKEDITOR.editor} sourceEditor
2183 * Private properties and methods.
2186 * @property {Object} _
2191 * Data transfer operation (drag and drop or copy and paste) started and ended in the same
2196 * @property {Number} [=1]
2199 CKEDITOR
.DATA_TRANSFER_INTERNAL
= 1;
2202 * Data transfer operation (drag and drop or copy and paste) started in one editor
2203 * instance and ended in another.
2207 * @property {Number} [=2]
2210 CKEDITOR
.DATA_TRANSFER_CROSS_EDITORS
= 2;
2213 * Data transfer operation (drag and drop or copy and paste) started outside of the editor.
2214 * The source of the data may be a textarea, HTML, another application, etc.
2218 * @property {Number} [=3]
2221 CKEDITOR
.DATA_TRANSFER_EXTERNAL
= 3;
2223 CKEDITOR
.plugins
.clipboard
.dataTransfer
.prototype = {
2225 * Facade for the native `getData` method.
2227 * @param {String} type The type of data to retrieve.
2228 * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist.
2230 getData: function( type
) {
2231 function isEmpty( data
) {
2232 return data
=== undefined || data
=== null || data
=== '';
2235 type
= this._
.normalizeType( type
);
2237 var data
= this._
.data
[ type
],
2240 if ( isEmpty( data
) ) {
2242 data
= this.$.getData( type
);
2246 if ( isEmpty( data
) ) {
2250 // Some browsers add <meta http-equiv="content-type" content="text/html; charset=utf-8"> at the begging of the HTML data
2251 // or surround it with <html><head>...</head><body>(some content)<!--StartFragment--> and <!--EndFragment-->(some content)</body></html>
2252 // This code removes meta tags and returns only the contents of the <body> element if found. Note that
2253 // some significant content may be placed outside Start/EndFragment comments so it's kept.
2255 // See #13583 for more details.
2256 if ( type
== 'text/html' ) {
2257 data
= data
.replace( this._
.metaRegExp
, '' );
2259 // Keep only contents of the <body> element
2260 result
= this._
.bodyRegExp
.exec( data
);
2261 if ( result
&& result
.length
) {
2264 // Remove also comments.
2265 data
= data
.replace( this._
.fragmentRegExp
, '' );
2268 // Firefox on Linux put files paths as a text/plain data if there are files
2269 // in the dataTransfer object. We need to hide it, because files should be
2270 // handled on paste only if dataValue is empty.
2271 else if ( type
== 'Text' && CKEDITOR
.env
.gecko
&& this.getFilesCount() &&
2272 data
.substring( 0, 7 ) == 'file://' ) {
2280 * Facade for the native `setData` method.
2282 * @param {String} type The type of data to retrieve.
2283 * @param {String} value The data to add.
2285 setData: function( type
, value
) {
2286 type
= this._
.normalizeType( type
);
2288 this._
.data
[ type
] = value
;
2290 // There is "Unexpected call to method or property access." error if you try
2291 // to set data of unsupported type on IE.
2292 if ( !CKEDITOR
.plugins
.clipboard
.isCustomDataTypesSupported
&& type
!= 'URL' && type
!= 'Text' ) {
2296 // If we use the text type to bind the ID, then if someone tries to set the text, we must also
2297 // update ID accordingly. #13468.
2298 if ( clipboardIdDataType
== 'Text' && type
== 'Text' ) {
2303 this.$.setData( type
, value
);
2308 * Gets the data transfer type.
2310 * @param {CKEDITOR.editor} targetEditor The drop/paste target editor instance.
2311 * @returns {Number} Possible values: {@link CKEDITOR#DATA_TRANSFER_INTERNAL},
2312 * {@link CKEDITOR#DATA_TRANSFER_CROSS_EDITORS}, {@link CKEDITOR#DATA_TRANSFER_EXTERNAL}.
2314 getTransferType: function( targetEditor
) {
2315 if ( !this.sourceEditor
) {
2316 return CKEDITOR
.DATA_TRANSFER_EXTERNAL
;
2317 } else if ( this.sourceEditor
== targetEditor
) {
2318 return CKEDITOR
.DATA_TRANSFER_INTERNAL
;
2320 return CKEDITOR
.DATA_TRANSFER_CROSS_EDITORS
;
2325 * Copies the data from the native data transfer to a private cache.
2326 * This function is needed because the data from the native data transfer
2327 * is available only synchronously to the event listener. It is not possible
2328 * to get the data asynchronously, after a timeout, and the {@link CKEDITOR.editor#paste}
2329 * event is fired asynchronously — hence the need for caching the data.
2331 cacheData: function() {
2339 function getAndSetData( type
) {
2340 type
= that
._
.normalizeType( type
);
2342 var data
= that
.getData( type
);
2344 that
._
.data
[ type
] = data
;
2349 if ( CKEDITOR
.plugins
.clipboard
.isCustomDataTypesSupported
) {
2350 if ( this.$.types
) {
2351 for ( i
= 0; i
< this.$.types
.length
; i
++ ) {
2352 getAndSetData( this.$.types
[ i
] );
2356 getAndSetData( 'Text' );
2357 getAndSetData( 'URL' );
2360 // Copy files references.
2361 file
= this._getImageFromClipboard();
2362 if ( ( this.$ && this.$.files
) || file
) {
2365 // Edge have empty files property with no length (#13755).
2366 if ( this.$.files
&& this.$.files
.length
) {
2367 for ( i
= 0; i
< this.$.files
.length
; i
++ ) {
2368 this._
.files
.push( this.$.files
[ i
] );
2372 // Don't include $.items if both $.files and $.items contains files, because,
2373 // according to spec and browsers behavior, they contain the same files.
2374 if ( this._
.files
.length
=== 0 && file
) {
2375 this._
.files
.push( file
);
2381 * Gets the number of files in the dataTransfer object.
2383 * @returns {Number} The number of files.
2385 getFilesCount: function() {
2386 if ( this._
.files
.length
) {
2387 return this._
.files
.length
;
2390 if ( this.$ && this.$.files
&& this.$.files
.length
) {
2391 return this.$.files
.length
;
2394 return this._getImageFromClipboard() ? 1 : 0;
2398 * Gets the file at the index given.
2400 * @param {Number} i Index.
2401 * @returns {File} File instance.
2403 getFile: function( i
) {
2404 if ( this._
.files
.length
) {
2405 return this._
.files
[ i
];
2408 if ( this.$ && this.$.files
&& this.$.files
.length
) {
2409 return this.$.files
[ i
];
2412 // File or null if the file was not found.
2413 return i
=== 0 ? this._getImageFromClipboard() : undefined;
2417 * Checks if the data transfer contains any data.
2419 * @returns {Boolean} `true` if the object contains no data.
2421 isEmpty: function() {
2422 var typesToCheck
= {},
2425 // If dataTransfer contains files it is not empty.
2426 if ( this.getFilesCount() ) {
2430 // Add custom types.
2431 for ( type
in this._
.data
) {
2432 typesToCheck
[ type
] = 1;
2435 // Add native types.
2437 if ( CKEDITOR
.plugins
.clipboard
.isCustomDataTypesSupported
) {
2438 if ( this.$.types
) {
2439 for ( var i
= 0; i
< this.$.types
.length
; i
++ ) {
2440 typesToCheck
[ this.$.types
[ i
] ] = 1;
2444 typesToCheck
.Text
= 1;
2445 typesToCheck
.URL
= 1;
2450 if ( clipboardIdDataType
!= 'Text' ) {
2451 typesToCheck
[ clipboardIdDataType
] = 0;
2454 for ( type
in typesToCheck
) {
2455 if ( typesToCheck
[ type
] && this.getData( type
) !== '' ) {
2464 * When the content of the clipboard is pasted in Chrome, the clipboard data object has an empty `files` property,
2465 * but it is possible to get the file as `items[0].getAsFile();` (#12961).
2468 * @returns {File} File instance or `null` if not found.
2470 _getImageFromClipboard: function() {
2473 if ( this.$ && this.$.items
&& this.$.items
[ 0 ] ) {
2475 file
= this.$.items
[ 0 ].getAsFile();
2477 if ( file
&& file
.type
) {
2491 * The default content type that is used when pasted data cannot be clearly recognized as HTML or text.
2493 * For example: `'foo'` may come from a plain text editor or a website. It is not possible to recognize the content
2494 * type in this case, so the default type will be used. At the same time it is clear that `'<b>example</b> text'` is
2495 * HTML and its origin is a web page, email or another rich text editor.
2497 * **Note:** If content type is text, then styles of the paste context are preserved.
2499 * CKEDITOR.config.clipboard_defaultContentType = 'text';
2501 * See also the {@link CKEDITOR.editor#paste} event and read more about the integration with clipboard
2502 * in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2505 * @cfg {'html'/'text'} [clipboard_defaultContentType='html']
2506 * @member CKEDITOR.config
2510 * Fired after the user initiated a paste action, but before the data is inserted into the editor.
2511 * The listeners to this event are able to process the content before its insertion into the document.
2513 * Read more about the integration with clipboard in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2517 * * the {@link CKEDITOR.config#pasteFilter} option,
2518 * * the {@link CKEDITOR.editor#drop} event,
2519 * * the {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2523 * @member CKEDITOR.editor
2524 * @param {CKEDITOR.editor} editor This editor instance.
2526 * @param {String} data.type The type of data in `data.dataValue`. Usually `'html'` or `'text'`, but for listeners
2527 * with a priority smaller than `6` it may also be `'auto'` which means that the content type has not been recognised yet
2528 * (this will be done by the content type sniffer that listens with priority `6`).
2529 * @param {String} data.dataValue HTML to be pasted.
2530 * @param {String} data.method Indicates the data transfer method. It could be drag and drop or copy and paste.
2531 * Possible values: `'drop'`, `'paste'`. Introduced in CKEditor 4.5.
2532 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer Facade for the native dataTransfer object
2533 * which provides access to various data types and files, and passes some data between linked events
2534 * (like drag and drop). Introduced in CKEditor 4.5.
2535 * @param {Boolean} [data.dontFilter=false] Whether the {@link CKEDITOR.editor#pasteFilter paste filter} should not
2536 * be applied to data. This option has no effect when `data.type` equals `'text'` which means that for instance
2537 * {@link CKEDITOR.config#forcePasteAsPlainText} has a higher priority. Introduced in CKEditor 4.5.
2541 * Fired before the {@link #paste} event. Allows to preset data type.
2543 * **Note:** This event is deprecated. Add a `0` priority listener for the
2544 * {@link #paste} event instead.
2547 * @event beforePaste
2548 * @member CKEDITOR.editor
2552 * Fired after the {@link #paste} event if content was modified. Note that if the paste
2553 * event does not insert any data, the `afterPaste` event will not be fired.
2556 * @member CKEDITOR.editor
2560 * Internal event to open the Paste dialog window.
2563 * @event pasteDialog
2564 * @member CKEDITOR.editor
2565 * @param {CKEDITOR.editor} editor This editor instance.
2566 * @param {Function} [data] Callback that will be passed to {@link CKEDITOR.editor#openDialog}.
2570 * Facade for the native `drop` event. Fired when the native `drop` event occurs.
2572 * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event.
2573 * Use the `drop` event only to control drag and drop operations (e.g. to prevent the ability to drop some content).
2575 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2579 * * The {@link CKEDITOR.editor#paste} event,
2580 * * The {@link CKEDITOR.editor#dragstart} and {@link CKEDITOR.editor#dragend} events,
2581 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2585 * @member CKEDITOR.editor
2586 * @param {CKEDITOR.editor} editor This editor instance.
2588 * @param {Object} data.$ Native drop event.
2589 * @param {CKEDITOR.dom.node} data.target Drop target.
2590 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2591 * @param {CKEDITOR.dom.range} data.dragRange Drag range, lets you manipulate the drag range.
2592 * Note that dragged HTML is saved as `text/html` data on `dragstart` so if you change the drag range
2593 * on drop, dropped HTML will not change. You need to change it manually using
2594 * {@link CKEDITOR.plugins.clipboard.dataTransfer#setData dataTransfer.setData}.
2595 * @param {CKEDITOR.dom.range} data.dropRange Drop range, lets you manipulate the drop range.
2599 * Facade for the native `dragstart` event. Fired when the native `dragstart` event occurs.
2601 * This event can be canceled in order to block the drag start operation. It can also be fired to mimic the start of the drag and drop
2602 * operation. For instance, the `widget` plugin uses this option to integrate its custom block widget drag and drop with
2603 * the entire system.
2605 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2609 * * The {@link CKEDITOR.editor#paste} event,
2610 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
2611 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2615 * @member CKEDITOR.editor
2616 * @param {CKEDITOR.editor} editor This editor instance.
2618 * @param {Object} data.$ Native dragstart event.
2619 * @param {CKEDITOR.dom.node} data.target Drag target.
2620 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2624 * Facade for the native `dragend` event. Fired when the native `dragend` event occurs.
2626 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2630 * * The {@link CKEDITOR.editor#paste} event,
2631 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
2632 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2636 * @member CKEDITOR.editor
2637 * @param {CKEDITOR.editor} editor This editor instance.
2639 * @param {Object} data.$ Native dragend event.
2640 * @param {CKEDITOR.dom.node} data.target Drag target.
2641 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2645 * Defines a filter which is applied to external data pasted or dropped into the editor. Possible values are:
2647 * * `'plain-text'` – Content will be pasted as a plain text.
2648 * * `'semantic-content'` – Known tags (except `div`, `span`) with all attributes (except
2649 * `style` and `class`) will be kept.
2650 * * `'h1 h2 p div'` – Custom rules compatible with {@link CKEDITOR.filter}.
2651 * * `null` – Content will not be filtered by the paste filter (but it still may be filtered
2652 * by [Advanced Content Filter](#!/guide/dev_advanced_content_filter)). This value can be used to
2653 * disable the paste filter in Chrome and Safari, where this option defaults to `'semantic-content'`.
2657 * config.pasteFilter = 'plain-text';
2661 * config.pasteFilter = 'h1 h2 p ul ol li; img[!src, alt]; a[!href]';
2663 * Based on this configuration option, a proper {@link CKEDITOR.filter} instance will be defined and assigned to the editor
2664 * as a {@link CKEDITOR.editor#pasteFilter}. You can tweak the paste filter settings on the fly on this object
2665 * as well as delete or replace it.
2667 * var editor = CKEDITOR.replace( 'editor', {
2668 * pasteFilter: 'semantic-content'
2671 * editor.on( 'instanceReady', function() {
2672 * // The result of this will be that all semantic content will be preserved
2674 * editor.pasteFilter.disallow( 'table' );
2677 * Note that the paste filter is applied only to **external** data. There are three data sources:
2679 * * copied and pasted in the same editor (internal),
2680 * * copied from one editor and pasted into another (cross-editor),
2681 * * coming from all other sources like websites, MS Word, etc. (external).
2683 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
2684 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
2685 * external data which often needs to be handled differently than content produced by the editor.
2687 * This setting defaults to `'semantic-content'` in Chrome, Opera and Safari (all Blink and Webkit based browsers)
2688 * due to messy HTML which these browsers keep in the clipboard. In other browsers it defaults to `null`.
2691 * @cfg {String} [pasteFilter='semantic-content' in Chrome and Safari and `null` in other browsers]
2692 * @member CKEDITOR.config
2696 * {@link CKEDITOR.filter Content filter} which is used when external data is pasted or dropped into the editor
2697 * or a forced paste as plain text occurs.
2699 * This object might be used on the fly to define rules for pasted external content.
2700 * This object is available and used if the {@link CKEDITOR.plugins.clipboard clipboard} plugin is enabled and
2701 * {@link CKEDITOR.config#pasteFilter} or {@link CKEDITOR.config#forcePasteAsPlainText} was defined.
2703 * To enable the filter:
2705 * var editor = CKEDITOR.replace( 'editor', {
2706 * pasteFilter: 'plain-text'
2709 * You can also modify the filter on the fly later on:
2711 * editor.pasteFilter = new CKEDITOR.filter( 'p h1 h2; a[!href]' );
2713 * Note that the paste filter is only applied to **external** data. There are three data sources:
2715 * * copied and pasted in the same editor (internal),
2716 * * copied from one editor and pasted into another (cross-editor),
2717 * * coming from all other sources like websites, MS Word, etc. (external).
2719 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
2720 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
2721 * external data which often needs to be handled differently than content produced by the editor.
2725 * @property {CKEDITOR.filter} [pasteFilter]
2726 * @member CKEDITOR.editor