]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/plugins/clipboard/plugin.js
Validation initiale
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / clipboard / plugin.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @ignore
8 * File overview: Clipboard support.
9 */
10
11 //
12 // COPY & PASTE EXECUTION FLOWS:
13 // -- CTRL+C
14 // * if ( isCustomCopyCutSupported )
15 // * dataTransfer.setData( 'text/html', getSelectedHtml )
16 // * else
17 // * browser's default behavior
18 // -- CTRL+X
19 // * listen onKey (onkeydown)
20 // * fire 'saveSnapshot' on editor
21 // * if ( isCustomCopyCutSupported )
22 // * dataTransfer.setData( 'text/html', getSelectedHtml )
23 // * extractSelectedHtml // remove selected contents
24 // * else
25 // * browser's default behavior
26 // * deferred second 'saveSnapshot' event
27 // -- CTRL+V
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
35 // -- Copy command
36 // * tryToCutCopy
37 // * execCommand
38 // * !success && notification
39 // -- Cut command
40 // * fixCut
41 // * tryToCutCopy
42 // * execCommand
43 // * !success && notification
44 // -- Paste command
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)
52 // * listen 'onpaste'
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
59 //
60 //
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)
66 // * html
67 // -- Possible flags:
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.
70 //
71 // -- Type: auto:
72 // * content: htmlified text -> filter, unify text markup (brs, ps, divs), set type: text
73 // * content: html -> filter, set type: html
74 // -- Type: text:
75 // * content: htmlified text -> filter, unify text markup
76 // * content: html -> filter, strip presentational markup, unify text markup
77 // -- Type: html:
78 // * content: htmlified text -> filter, unify text markup
79 // * content: html -> filter
80 //
81 // -- Phases:
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)
86 //
87 // DRAG & DROP EXECUTION FLOWS:
88 // -- Drag
89 // * save to the global object:
90 // * drag timestamp (with 'cke-' prefix),
91 // * selected html,
92 // * drag range,
93 // * editor instance.
94 // * put drag timestamp into event.dataTransfer.text
95 // -- Drop
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
114
115 'use strict';
116
117 ( function() {
118 // Register the plugin.
119 CKEDITOR.plugins.add( 'clipboard', {
120 requires: 'dialog',
121 // 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%
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 ) {
127 var filterType,
128 filtersFactory = filtersFactoryFactory();
129
130 if ( editor.config.forcePasteAsPlainText ) {
131 filterType = 'plain-text';
132 } else if ( editor.config.pasteFilter ) {
133 filterType = editor.config.pasteFilter;
134 }
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';
139 }
140
141 editor.pasteFilter = filtersFactory.get( filterType );
142
143 initPasteClipboard( editor );
144 initDragDrop( editor );
145
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
149 // step as the conversion is asynchronous and should hold all further paste processing.
150 if ( CKEDITOR.env.gecko ) {
151 var supportedImageTypes = [ 'image/png', 'image/jpeg', 'image/gif' ],
152 latestId;
153
154 editor.on( 'paste', function( evt ) {
155 var dataObj = evt.data,
156 data = dataObj.dataValue,
157 dataTransfer = dataObj.dataTransfer;
158
159 // If data empty check for image content inside data transfer. #16705
160 if ( !data && dataObj.method == 'paste' && dataTransfer && dataTransfer.getFilesCount() == 1 && latestId != dataTransfer.id ) {
161 var file = dataTransfer.getFile( 0 );
162
163 if ( CKEDITOR.tools.indexOf( supportedImageTypes, file.type ) != -1 ) {
164 var fileReader = new FileReader();
165
166 // Convert image file to img tag with base64 image.
167 fileReader.addEventListener( 'load', function() {
168 evt.data.dataValue = '<img src="' + fileReader.result + '" />';
169 editor.fire( 'paste', evt.data );
170 }, false );
171
172 // Proceed with normal flow if reading file was aborted.
173 fileReader.addEventListener( 'abort', function() {
174 editor.fire( 'paste', evt.data );
175 }, false );
176
177 // Proceed with normal flow if reading file failed.
178 fileReader.addEventListener( 'error', function() {
179 editor.fire( 'paste', evt.data );
180 }, false );
181
182 fileReader.readAsDataURL( file );
183
184 latestId = dataObj.dataTransfer.id;
185
186 evt.stop();
187 }
188 }
189 }, null, null, 1 );
190 }
191
192 editor.on( 'paste', function( evt ) {
193 // Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
194 if ( !evt.data.dataTransfer ) {
195 evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
196 }
197
198 // If dataValue is already set (manually or by paste bin), so do not override it.
199 if ( evt.data.dataValue ) {
200 return;
201 }
202
203 var dataTransfer = evt.data.dataTransfer,
204 // IE support only text data and throws exception if we try to get html data.
205 // This html data object may also be empty if we drag content of the textarea.
206 value = dataTransfer.getData( 'text/html' );
207
208 if ( value ) {
209 evt.data.dataValue = value;
210 evt.data.type = 'html';
211 } else {
212 // Try to get text data otherwise.
213 value = dataTransfer.getData( 'text/plain' );
214
215 if ( value ) {
216 evt.data.dataValue = editor.editable().transformPlainTextToHtml( value );
217 evt.data.type = 'text';
218 }
219 }
220 }, null, null, 1 );
221
222 editor.on( 'paste', function( evt ) {
223 var data = evt.data.dataValue,
224 blockElements = CKEDITOR.dtd.$block;
225
226 // Filter webkit garbage.
227 if ( data.indexOf( 'Apple-' ) > -1 ) {
228 // Replace special webkit's &nbsp; with simple space, because webkit
229 // produces them even for normal spaces.
230 data = data.replace( /<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ' );
231
232 // Strip <span> around white-spaces when not in forced 'html' content type.
233 // This spans are created only when pasting plain text into Webkit,
234 // but for safety reasons remove them always.
235 if ( evt.data.type != 'html' ) {
236 data = data.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi, function( all, spaces ) {
237 // Replace tabs with 4 spaces like Fx does.
238 return spaces.replace( /\t/g, '&nbsp;&nbsp; &nbsp;' );
239 } );
240 }
241
242 // This br is produced only when copying & pasting HTML content.
243 if ( data.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
244 evt.data.startsWithEOL = 1;
245 evt.data.preSniffing = 'html'; // Mark as not text.
246 data = data.replace( /<br class="Apple-interchange-newline">/, '' );
247 }
248
249 // Remove all other classes.
250 data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
251 }
252
253 // Strip editable that was copied from inside. (#9534)
254 if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) {
255 var tmp,
256 editable_wrapper,
257 wrapper = new CKEDITOR.dom.element( 'div' );
258
259 wrapper.setHtml( data );
260 // Verify for sure and check for nested editor UI parts. (#9675)
261 while ( wrapper.getChildCount() == 1 &&
262 ( tmp = wrapper.getFirst() ) &&
263 tmp.type == CKEDITOR.NODE_ELEMENT && // Make sure first-child is element.
264 ( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) {
265 wrapper = editable_wrapper = tmp;
266 }
267
268 // If editable wrapper was found strip it and bogus <br> (added on FF).
269 if ( editable_wrapper )
270 data = editable_wrapper.getHtml().replace( /<br>$/i, '' );
271 }
272
273 if ( CKEDITOR.env.ie ) {
274 // &nbsp; <p> -> <p> (br.cke-pasted-remove will be removed later)
275 data = data.replace( /^&nbsp;(?: |\r\n)?<(\w+)/g, function( match, elementName ) {
276 if ( elementName.toLowerCase() in blockElements ) {
277 evt.data.preSniffing = 'html'; // Mark as not a text.
278 return '<' + elementName;
279 }
280 return match;
281 } );
282 } else if ( CKEDITOR.env.webkit ) {
283 // </p><div><br></div> -> </p><br>
284 // We don't mark br, because this situation can happen for htmlified text too.
285 data = data.replace( /<\/(\w+)><div><br><\/div>$/, function( match, elementName ) {
286 if ( elementName in blockElements ) {
287 evt.data.endsWithEOL = 1;
288 return '</' + elementName + '>';
289 }
290 return match;
291 } );
292 } else if ( CKEDITOR.env.gecko ) {
293 // Firefox adds bogus <br> when user pasted text followed by space(s).
294 data = data.replace( /(\s)<br>$/, '$1' );
295 }
296
297 evt.data.dataValue = data;
298 }, null, null, 3 );
299
300 editor.on( 'paste', function( evt ) {
301 var dataObj = evt.data,
302 type = dataObj.type,
303 data = dataObj.dataValue,
304 trueType,
305 // Default is 'html'.
306 defaultType = editor.config.clipboard_defaultContentType || 'html',
307 transferType = dataObj.dataTransfer.getTransferType( editor );
308
309 // If forced type is 'html' we don't need to know true data type.
310 if ( type == 'html' || dataObj.preSniffing == 'html' ) {
311 trueType = 'html';
312 } else {
313 trueType = recogniseContentType( data );
314 }
315
316 // Unify text markup.
317 if ( trueType == 'htmlifiedtext' ) {
318 data = htmlifiedTextHtmlification( editor.config, data );
319 }
320
321 // Strip presentational markup & unify text markup.
322 // Forced plain text (dialog or forcePAPT).
323 // 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
325 // forcePAPT should have priority as it had before 4.5.
326 if ( type == 'text' && trueType == 'html' ) {
327 data = filterContent( editor, data, filtersFactory.get( 'plain-text' ) );
328 }
329 // External paste and pasteFilter exists and filtering isn't disabled.
330 else if ( transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL && editor.pasteFilter && !dataObj.dontFilter ) {
331 data = filterContent( editor, data, editor.pasteFilter );
332 }
333
334 if ( dataObj.startsWithEOL ) {
335 data = '<br data-cke-eol="1">' + data;
336 }
337 if ( dataObj.endsWithEOL ) {
338 data += '<br data-cke-eol="1">';
339 }
340
341 if ( type == 'auto' ) {
342 type = ( trueType == 'html' || defaultType == 'html' ) ? 'html' : 'text';
343 }
344
345 dataObj.type = type;
346 dataObj.dataValue = data;
347 delete dataObj.preSniffing;
348 delete dataObj.startsWithEOL;
349 delete dataObj.endsWithEOL;
350 }, null, null, 6 );
351
352 // Inserts processed data into the editor at the end of the
353 // events chain.
354 editor.on( 'paste', function( evt ) {
355 var data = evt.data;
356
357 if ( data.dataValue ) {
358 editor.insertHtml( data.dataValue, data.type, data.range );
359
360 // Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
361 // Fire afterPaste only if paste inserted some HTML.
362 setTimeout( function() {
363 editor.fire( 'afterPaste' );
364 }, 0 );
365 }
366 }, 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 }
379 } );
380
381 function firePasteEvents( editor, data, withBeforePaste ) {
382 if ( !data.type ) {
383 data.type = 'auto';
384 }
385
386 if ( withBeforePaste ) {
387 // Fire 'beforePaste' event so clipboard flavor get customized
388 // by other plugins.
389 if ( editor.fire( 'beforePaste', data ) === false )
390 return false; // Event canceled
391 }
392
393 // Do not fire paste if there is no data (dataValue and dataTranfser are empty).
394 // This check should be done after firing 'beforePaste' because for native paste
395 // 'beforePaste' is by default fired even for empty clipboard.
396 if ( !data.dataValue && data.dataTransfer.isEmpty() ) {
397 return false;
398 }
399
400 if ( !data.dataValue ) {
401 data.dataValue = '';
402 }
403
404 // Because of FF bug we need to use this hack, otherwise cursor is hidden
405 // or it is not possible to move it (#12420).
406 // Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (#13305).
407 if ( CKEDITOR.env.gecko && data.method == 'drop' && editor.toolbox ) {
408 editor.once( 'afterPaste', function() {
409 editor.toolbox.focus();
410 } );
411 }
412
413 return editor.fire( 'paste', data );
414 }
415
416 function initPasteClipboard( editor ) {
417 var clipboard = CKEDITOR.plugins.clipboard,
418 preventBeforePasteEvent = 0,
419 preventPasteEvent = 0,
420 inReadOnly = 0;
421
422 addListeners();
423 addButtonsCommands();
424
425 /**
426 * Gets clipboard data by directly accessing the clipboard (IE only) or opening the paste dialog window.
427 *
428 * editor.getClipboardData( { title: 'Get my data' }, function( data ) {
429 * if ( data )
430 * alert( data.type + ' ' + data.dataValue );
431 * } );
432 *
433 * @member CKEDITOR.editor
434 * @param {Object} options
435 * @param {String} [options.title] The title of the paste dialog window.
436 * @param {Function} callback A function that will be executed with `data.type` and `data.dataValue`
437 * or `null` if none of the capturing methods succeeded.
438 */
439 editor.getClipboardData = function( options, callback ) {
440 var beforePasteNotCanceled = false,
441 dataType = 'auto',
442 dialogCommited = false;
443
444 // Options are optional - args shift.
445 if ( !callback ) {
446 callback = options;
447 options = null;
448 }
449
450 // Listen with maximum priority to handle content before everyone else.
451 // This callback will handle paste event that will be fired if direct
452 // access to the clipboard succeed in IE.
453 editor.on( 'paste', onPaste, null, null, 0 );
454
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
463 // user agrees) open and handle paste dialog.
464 if ( getClipboardDataDirectly() === false ) {
465 // Direct access to the clipboard wasn't successful so remove listener.
466 editor.removeListener( 'paste', onPaste );
467
468 // If beforePaste was canceled do not open dialog.
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 }
489
490 function onPaste( evt ) {
491 evt.removeListener();
492 evt.cancel();
493 callback( evt.data );
494 }
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 };
520
521 function addButtonsCommands() {
522 addButtonCommand( 'Cut', 'cut', createCutCopyCmd( 'cut' ), 10, 1 );
523 addButtonCommand( 'Copy', 'copy', createCutCopyCmd( 'copy' ), 20, 4 );
524 addButtonCommand( 'Paste', 'paste', createPasteCmd(), 30, 8 );
525
526 function addButtonCommand( buttonName, commandName, command, toolbarOrder, ctxMenuOrder ) {
527 var lang = editor.lang.clipboard[ commandName ];
528
529 editor.addCommand( commandName, command );
530 editor.ui.addButton && editor.ui.addButton( buttonName, {
531 label: lang,
532 command: commandName,
533 toolbar: 'clipboard,' + toolbarOrder
534 } );
535
536 // If the "menu" plugin is loaded, register the menu item.
537 if ( editor.addMenuItems ) {
538 editor.addMenuItem( commandName, {
539 label: lang,
540 command: commandName,
541 group: 'clipboard',
542 order: ctxMenuOrder
543 } );
544 }
545 }
546 }
547
548 function addListeners() {
549 editor.on( 'key', onKey );
550 editor.on( 'contentDom', addPasteListenersToEditable );
551
552 // For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that.
553 editor.on( 'selectionChange', function( evt ) {
554 inReadOnly = evt.data.selection.getRanges()[ 0 ].checkReadOnly();
555 setToolbarStates();
556 } );
557
558 // If the "contextmenu" plugin is loaded, register the listeners.
559 if ( editor.contextMenu ) {
560 editor.contextMenu.addListener( function( element, selection ) {
561 inReadOnly = selection.getRanges()[ 0 ].checkReadOnly();
562 return {
563 cut: stateFromNamedCommand( 'cut' ),
564 copy: stateFromNamedCommand( 'copy' ),
565 paste: stateFromNamedCommand( 'paste' )
566 };
567 } );
568 }
569 }
570
571 // Add events listeners to editable.
572 function addPasteListenersToEditable() {
573 var editable = editor.editable();
574
575 if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
576 var initOnCopyCut = function( evt ) {
577 // If user tries to cut in read-only editor, we must prevent default action. (#13872)
578 if ( !editor.readOnly || evt.name != 'cut' ) {
579 clipboard.initPasteDataTransfer( evt, editor );
580 }
581 evt.data.preventDefault();
582 };
583
584 editable.on( 'copy', initOnCopyCut );
585 editable.on( 'cut', initOnCopyCut );
586
587 // Delete content with the low priority so one can overwrite cut data.
588 editable.on( 'cut', function() {
589 // If user tries to cut in read-only editor, we must prevent default action. (#13872)
590 if ( !editor.readOnly ) {
591 editor.extractSelectedHtml();
592 }
593 }, null, null, 999 );
594 }
595
596 // We'll be catching all pasted content in one line, regardless of whether
597 // it's introduced by a document command execution (e.g. toolbar buttons) or
598 // user paste behaviors (e.g. CTRL+V).
599 editable.on( clipboard.mainPasteEvent, function( evt ) {
600 if ( clipboard.mainPasteEvent == 'beforepaste' && preventBeforePasteEvent ) {
601 return;
602 }
603
604 // If you've just asked yourself why preventPasteEventNow() is not here, but
605 // in listener for CTRL+V and exec method of 'paste' command
606 // you've asked the same question we did.
607 //
608 // THE ANSWER:
609 //
610 // First thing to notice - this answer makes sense only for IE,
611 // because other browsers don't listen for 'paste' event.
612 //
613 // What would happen if we move preventPasteEventNow() here?
614 // For:
615 // * CTRL+V - IE fires 'beforepaste', so we prevent 'paste' and pasteDataFromClipboard(). OK.
616 // * editor.execCommand( 'paste' ) - we fire 'beforepaste', so we prevent
617 // 'paste' and pasteDataFromClipboard() and doc.execCommand( 'Paste' ). OK.
618 // * native context menu - IE fires 'beforepaste', so we prevent 'paste', but unfortunately
619 // on IE we fail with pasteDataFromClipboard() here, because of... we don't know why, but
620 // we just fail, so... we paste nothing. FAIL.
621 // * native menu bar - the same as for native context menu.
622 //
623 // But don't you know any way to distinguish first two cases from last two?
624 // Only one - special flag set in CTRL+V handler and exec method of 'paste'
625 // command. And that's what we did using preventPasteEventNow().
626
627 pasteDataFromClipboard( evt );
628 } );
629
630 // It's not possible to clearly handle all four paste methods (ctrl+v, native menu bar
631 // native context menu, editor's command) in one 'paste/beforepaste' event in IE.
632 //
633 // For ctrl+v & editor's command it's easy to handle pasting in 'beforepaste' listener,
634 // so we do this. For another two methods it's better to use 'paste' event.
635 //
636 // 'paste' is always being fired after 'beforepaste' (except of weird one on opening native
637 // context menu), so for two methods handled in 'beforepaste' we're canceling 'paste'
638 // using preventPasteEvent state.
639 //
640 // 'paste' event in IE is being fired before getClipboardDataByPastebin executes its callback.
641 //
642 // QUESTION: Why didn't you handle all 4 paste methods in handler for 'paste'?
643 // Wouldn't this just be simpler?
644 // ANSWER: Then we would have to evt.data.preventDefault() only for native
645 // context menu and menu bar pastes. The same with execIECommand().
646 // That would force us to mark CTRL+V and editor's paste command with
647 // special flag, other than preventPasteEvent. But we still would have to
648 // have preventPasteEvent for the second event fired by execIECommand.
649 // Code would be longer and not cleaner.
650 if ( clipboard.mainPasteEvent == 'beforepaste' ) {
651 editable.on( 'paste', function( evt ) {
652 if ( preventPasteEvent ) {
653 return;
654 }
655
656 // Cancel next 'paste' event fired by execIECommand( 'paste' )
657 // at the end of this callback.
658 preventPasteEventNow();
659
660 // Prevent native paste.
661 evt.data.preventDefault();
662
663 pasteDataFromClipboard( evt );
664
665 // Force IE to paste content into pastebin so pasteDataFromClipboard will work.
666 if ( !execIECommand( 'paste' ) ) {
667 editor.openDialog( 'paste' );
668 }
669 } );
670
671 // If mainPasteEvent is 'beforePaste' (IE before Edge),
672 // dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953)
673 editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 );
674
675 editable.on( 'beforepaste', function( evt ) {
676 // Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (#11970).
677 if ( evt.data && !evt.data.$.ctrlKey && !evt.data.$.shiftKey )
678 preventBeforePasteEventNow();
679 }, null, null, 0 );
680 }
681
682 editable.on( 'beforecut', function() {
683 !preventBeforePasteEvent && fixCut( editor );
684 } );
685
686 var mouseupTimeout;
687
688 // 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
690 // iframe and ended out of the editor (#9851).
691 editable.attachListener( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement(), 'mouseup', function() {
692 mouseupTimeout = setTimeout( function() {
693 setToolbarStates();
694 }, 0 );
695 } );
696
697 // 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
699 // with mouseup event (i.e. a button with onclick callback) (#10219).
700 editor.on( 'destroy', function() {
701 clearTimeout( mouseupTimeout );
702 } );
703
704 editable.on( 'keyup', setToolbarStates );
705 }
706
707 // Create object representing Cut or Copy commands.
708 function createCutCopyCmd( type ) {
709 return {
710 type: type,
711 canUndo: type == 'cut', // We can't undo copy to clipboard.
712 startDisabled: true,
713 fakeKeystroke: type == 'cut' ? CKEDITOR.CTRL + 88 /*X*/ : CKEDITOR.CTRL + 67 /*C*/,
714 exec: function() {
715 // Attempts to execute the Cut and Copy operations.
716 function tryToCutCopy( type ) {
717 if ( CKEDITOR.env.ie )
718 return execIECommand( type );
719
720 // non-IEs part
721 try {
722 // Other browsers throw an error if the command is disabled.
723 return editor.document.$.execCommand( type, false, null );
724 } catch ( e ) {
725 return false;
726 }
727 }
728
729 this.type == 'cut' && fixCut();
730
731 var success = tryToCutCopy( this.type );
732
733 if ( !success ) {
734 // Show cutError or copyError.
735 editor.showNotification( editor.lang.clipboard[ this.type + 'Error' ] ); // jshint ignore:line
736 }
737
738 return success;
739 }
740 };
741 }
742
743 function createPasteCmd() {
744 return {
745 // Snapshots are done manually by editable.insertXXX methods.
746 canUndo: false,
747 async: true,
748 fakeKeystroke: CKEDITOR.CTRL + 86 /*V*/,
749 exec: function( editor, data ) {
750 var cmd = this,
751 fire = function( data, withBeforePaste ) {
752 data && firePasteEvents( editor, data, !!withBeforePaste );
753
754 editor.fire( 'afterCommandExec', {
755 name: 'paste',
756 command: cmd,
757 returnValue: !!data
758 } );
759 };
760
761 // Check data precisely - don't open dialog on empty string.
762 if ( typeof data == 'string' )
763 fire( {
764 dataValue: data,
765 method: 'paste',
766 dataTransfer: clipboard.initPasteDataTransfer()
767 }, 1 );
768 else
769 editor.getClipboardData( fire );
770 }
771 };
772 }
773
774 function preventPasteEventNow() {
775 preventPasteEvent = 1;
776 // For safety reason we should wait longer than 0/1ms.
777 // We don't know how long execution of quite complex getClipboardData will take
778 // and in for example 'paste' listener execCommand() (which fires 'paste') is called
779 // after getClipboardData finishes.
780 // Luckily, it's impossible to immediately fire another 'paste' event we want to handle,
781 // because we only handle there native context menu and menu bar.
782 setTimeout( function() {
783 preventPasteEvent = 0;
784 }, 100 );
785 }
786
787 function preventBeforePasteEventNow() {
788 preventBeforePasteEvent = 1;
789 setTimeout( function() {
790 preventBeforePasteEvent = 0;
791 }, 10 );
792 }
793
794 // Tries to execute any of the paste, cut or copy commands in IE. Returns a
795 // boolean indicating that the operation succeeded.
796 // @param {String} command *LOWER CASED* name of command ('paste', 'cut', 'copy').
797 function execIECommand( command ) {
798 var doc = editor.document,
799 body = doc.getBody(),
800 enabled = false,
801 onExec = function() {
802 enabled = true;
803 };
804
805 // The following seems to be the only reliable way to detect that
806 // clipboard commands are enabled in IE. It will fire the
807 // onpaste/oncut/oncopy events only if the security settings allowed
808 // the command to execute.
809 body.on( command, onExec );
810
811 // IE7: document.execCommand has problem to paste into positioned element.
812 if ( CKEDITOR.env.version > 7 ) {
813 doc.$.execCommand( command );
814 } else {
815 doc.$.selection.createRange().execCommand( command );
816 }
817
818 body.removeListener( command, onExec );
819
820 return enabled;
821 }
822
823 // Cutting off control type element in IE standards breaks the selection entirely. (#4881)
824 function fixCut() {
825 if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks )
826 return;
827
828 var sel = editor.getSelection(),
829 control, range, dummy;
830
831 if ( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) {
832 range = sel.getRanges()[ 0 ];
833 dummy = editor.document.createText( '' );
834 dummy.insertBefore( control );
835 range.setStartBefore( dummy );
836 range.setEndAfter( control );
837 sel.selectRanges( [ range ] );
838
839 // Clear up the fix if the paste wasn't succeeded.
840 setTimeout( function() {
841 // Element still online?
842 if ( control.getParent() ) {
843 dummy.remove();
844 sel.selectElement( control );
845 }
846 }, 0 );
847 }
848 }
849
850 // Allow to peek clipboard content by redirecting the
851 // pasting content into a temporary bin and grab the content of it.
852 function getClipboardDataByPastebin( evt, callback ) {
853 var doc = editor.document,
854 editable = editor.editable(),
855 cancel = function( evt ) {
856 evt.cancel();
857 },
858 blurListener;
859
860 // Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
861 if ( doc.getById( 'cke_pastebin' ) )
862 return;
863
864 var sel = editor.getSelection();
865 var bms = sel.createBookmarks();
866
867 // #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.
869 // selectionchange fired before getClipboardDataByPastebin() cached selection
870 // before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM),
871 // so we need to fire selectionchange one more time, to store current seleciton.
872 // Selection will be locked when we focus pastebin.
873 if ( CKEDITOR.env.ie )
874 sel.root.fire( 'selectionchange' );
875
876 // Create container to paste into.
877 // For rich content we prefer to use "body" since it holds
878 // the least possibility to be splitted by pasted content, while this may
879 // breaks the text selection on a frame-less editable, "div" would be
880 // the best one in that case.
881 // In another case on old IEs moving the selection into a "body" paste bin causes error panic.
882 // Body can't be also used for Opera which fills it with <br>
883 // what is indistinguishable from pasted <br> (copying <br> in Opera isn't possible,
884 // but it can be copied from other browser).
885 var pastebin = new CKEDITOR.dom.element(
886 ( CKEDITOR.env.webkit || editable.is( 'body' ) ) && !CKEDITOR.env.ie ? 'body' : 'div', doc );
887
888 pastebin.setAttributes( {
889 id: 'cke_pastebin',
890 'data-cke-temp': '1'
891 } );
892
893 var containerOffset = 0,
894 offsetParent,
895 win = doc.getWindow();
896
897 if ( CKEDITOR.env.webkit ) {
898 // 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.
900 editable.append( pastebin );
901 // Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754)
902 pastebin.addClass( 'cke_editable' );
903
904 // Compensate position of offsetParent.
905 if ( !editable.is( 'body' ) ) {
906 // We're not able to get offsetParent from pastebin (body element), so check whether
907 // its parent (editable) is positioned.
908 if ( editable.getComputedStyle( 'position' ) != 'static' )
909 offsetParent = editable;
910 // And if not - safely get offsetParent from editable.
911 else
912 offsetParent = CKEDITOR.dom.element.get( editable.$.offsetParent );
913
914 containerOffset = offsetParent.getDocumentPosition().y;
915 }
916 } else {
917 // Opera and IE doesn't allow to append to html element.
918 editable.getAscendant( CKEDITOR.env.ie ? 'body' : 'html', 1 ).append( pastebin );
919 }
920
921 pastebin.setStyles( {
922 position: 'absolute',
923 // Position the bin at the top (+10 for safety) of viewport to avoid any subsequent document scroll.
924 top: ( win.getScrollPosition().y - containerOffset + 10 ) + 'px',
925 width: '1px',
926 // Caret has to fit in that height, otherwise browsers like Chrome & Opera will scroll window to show it.
927 // Set height equal to viewport's height - 20px (safety gaps), minimum 1px.
928 height: Math.max( 1, win.getViewPaneSize().height - 20 ) + 'px',
929 overflow: 'hidden',
930 // Reset styles that can mess up pastebin position.
931 margin: 0,
932 padding: 0
933 } );
934
935 // Paste fails in Safari when the body tag has 'user-select: none'. (#12506)
936 if ( CKEDITOR.env.safari )
937 pastebin.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'text' ) );
938
939 // Check if the paste bin now establishes new editing host.
940 var isEditingHost = pastebin.getParent().isReadOnly();
941
942 if ( isEditingHost ) {
943 // Hide the paste bin.
944 pastebin.setOpacity( 0 );
945 // And make it editable.
946 pastebin.setAttribute( 'contenteditable', true );
947 }
948 // Transparency is not enough since positioned non-editing host always shows
949 // resize handler, pull it off the screen instead.
950 else {
951 pastebin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-10000px' );
952 }
953
954 editor.on( 'selectionChange', cancel, null, null, 0 );
955
956 // Webkit fill fire blur on editable when moving selection to
957 // pastebin (if body is used). Cancel it because it causes incorrect
958 // selection lock in case of inline editor (#10644).
959 // The same seems to apply to Firefox (#10787).
960 if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko )
961 blurListener = editable.once( 'blur', cancel, null, null, -100 );
962
963 // Temporarily move selection to the pastebin.
964 isEditingHost && pastebin.focus();
965 var range = new CKEDITOR.dom.range( pastebin );
966 range.selectNodeContents( pastebin );
967 var selPastebin = range.select();
968
969 // 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
971 // this selection will be restored. We overwrite stored selection, so it's restored
972 // in pastebin. (#9552)
973 if ( CKEDITOR.env.ie ) {
974 blurListener = editable.once( 'blur', function() {
975 editor.lockSelection( selPastebin );
976 } );
977 }
978
979 var scrollTop = CKEDITOR.document.getWindow().getScrollPosition().y;
980
981 // Wait a while and grab the pasted contents.
982 setTimeout( function() {
983 // Restore main window's scroll position which could have been changed
984 // by browser in cases described in #9771.
985 if ( CKEDITOR.env.webkit )
986 CKEDITOR.document.getBody().$.scrollTop = scrollTop;
987
988 // Blur will be fired only on non-native paste. In other case manually remove listener.
989 blurListener && blurListener.removeListener();
990
991 // Restore properly the document focus. (#8849)
992 if ( CKEDITOR.env.ie )
993 editable.focus();
994
995 // IE7: selection must go before removing pastebin. (#8691)
996 sel.selectBookmarks( bms );
997 pastebin.remove();
998
999 // Grab the HTML contents.
1000 // We need to look for a apple style wrapper on webkit it also adds
1001 // a div wrapper if you copy/paste the body of the editor.
1002 // Remove hidden div and restore selection.
1003 var bogusSpan;
1004 if ( CKEDITOR.env.webkit && ( bogusSpan = pastebin.getFirst() ) && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) )
1005 pastebin = bogusSpan;
1006
1007 editor.removeListener( 'selectionChange', cancel );
1008 callback( pastebin.getHtml() );
1009 }, 0 );
1010 }
1011
1012 // Try to get content directly on IE from clipboard, without native event
1013 // being fired before. In other words - synthetically get clipboard data, if it's possible.
1014 // mainPasteEvent will be fired, so if forced native paste:
1015 // * worked, getClipboardDataByPastebin will grab it,
1016 // * didn't work, dataValue and dataTransfer will be empty and editor#paste won't be fired.
1017 // Clipboard data can be accessed directly only on IEs older than Edge.
1018 // On other browsers we should fire beforePaste event and return false.
1019 function getClipboardDataDirectly() {
1020 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' } );
1023 return false;
1024 }
1025
1026 // Prevent IE from pasting at the begining of the document.
1027 editor.focus();
1028
1029 // Command will be handled by 'beforepaste', but as
1030 // execIECommand( 'paste' ) will fire also 'paste' event
1031 // we're canceling it.
1032 preventPasteEventNow();
1033
1034 // #9247: Lock focus to prevent IE from hiding toolbar for inline editor.
1035 var focusManager = editor.focusManager;
1036 focusManager.lock();
1037
1038 if ( editor.editable().fire( clipboard.mainPasteEvent ) && !execIECommand( 'paste' ) ) {
1039 focusManager.unlock();
1040 return false;
1041 }
1042 focusManager.unlock();
1043
1044 return true;
1045 }
1046
1047 // Listens for some clipboard related keystrokes, so they get customized.
1048 // Needs to be bind to keydown event.
1049 function onKey( event ) {
1050 if ( editor.mode != 'wysiwyg' )
1051 return;
1052
1053 switch ( event.data.keyCode ) {
1054 // Paste
1055 case CKEDITOR.CTRL + 86: // CTRL+V
1056 case CKEDITOR.SHIFT + 45: // SHIFT+INS
1057 var editable = editor.editable();
1058
1059 // Cancel 'paste' event because ctrl+v is for IE handled
1060 // by 'beforepaste'.
1061 preventPasteEventNow();
1062
1063 // Simulate 'beforepaste' event for all browsers using 'paste' as main event.
1064 if ( clipboard.mainPasteEvent == 'paste' ) {
1065 editable.fire( 'beforepaste' );
1066 }
1067
1068 return;
1069
1070 // Cut
1071 case CKEDITOR.CTRL + 88: // CTRL+X
1072 case CKEDITOR.SHIFT + 46: // SHIFT+DEL
1073 // Save Undo snapshot.
1074 editor.fire( 'saveSnapshot' ); // Save before cut
1075 setTimeout( function() {
1076 editor.fire( 'saveSnapshot' ); // Save after cut
1077 }, 50 ); // OSX is slow (#11416).
1078 }
1079 }
1080
1081 function pasteDataFromClipboard( evt ) {
1082 // Default type is 'auto', but can be changed by beforePaste listeners.
1083 var eventData = {
1084 type: 'auto',
1085 method: 'paste',
1086 dataTransfer: clipboard.initPasteDataTransfer( evt )
1087 };
1088
1089 eventData.dataTransfer.cacheData();
1090
1091 // Fire 'beforePaste' event so clipboard flavor get customized by other plugins.
1092 // If 'beforePaste' is canceled continue executing getClipboardDataByPastebin and then do nothing
1093 // (do not fire 'paste', 'afterPaste' events). This way we can grab all - synthetically
1094 // and natively pasted content and prevent its insertion into editor
1095 // after canceling 'beforePaste' event.
1096 var beforePasteNotCanceled = editor.fire( 'beforePaste', eventData ) !== false;
1097
1098 // Do not use paste bin if the browser let us get HTML or files from dataTranfer.
1099 if ( beforePasteNotCanceled && clipboard.canClipboardApiBeTrusted( eventData.dataTransfer, editor ) ) {
1100 evt.data.preventDefault();
1101 setTimeout( function() {
1102 firePasteEvents( editor, eventData );
1103 }, 0 );
1104 } else {
1105 getClipboardDataByPastebin( evt, function( data ) {
1106 // Clean up.
1107 eventData.dataValue = data.replace( /<span[^>]+data-cke-bookmark[^<]*?<\/span>/ig, '' );
1108
1109 // Fire remaining events (without beforePaste)
1110 beforePasteNotCanceled && firePasteEvents( editor, eventData );
1111 } );
1112 }
1113 }
1114
1115 function setToolbarStates() {
1116 if ( editor.mode != 'wysiwyg' )
1117 return;
1118
1119 var pasteState = stateFromNamedCommand( 'paste' );
1120
1121 editor.getCommand( 'cut' ).setState( stateFromNamedCommand( 'cut' ) );
1122 editor.getCommand( 'copy' ).setState( stateFromNamedCommand( 'copy' ) );
1123 editor.getCommand( 'paste' ).setState( pasteState );
1124 editor.fire( 'pasteState', pasteState );
1125 }
1126
1127 function stateFromNamedCommand( command ) {
1128 if ( inReadOnly && command in { paste: 1, cut: 1 } )
1129 return CKEDITOR.TRISTATE_DISABLED;
1130
1131 if ( command == 'paste' )
1132 return CKEDITOR.TRISTATE_OFF;
1133
1134 // Cut, copy - check if the selection is not empty.
1135 var sel = editor.getSelection(),
1136 ranges = sel.getRanges(),
1137 selectionIsEmpty = sel.getType() == CKEDITOR.SELECTION_NONE || ( ranges.length == 1 && ranges[ 0 ].collapsed );
1138
1139 return selectionIsEmpty ? CKEDITOR.TRISTATE_DISABLED : CKEDITOR.TRISTATE_OFF;
1140 }
1141 }
1142
1143 // Returns:
1144 // * 'htmlifiedtext' if content looks like transformed by browser from plain text.
1145 // See clipboard/paste.html TCs for more info.
1146 // * 'html' if it is not 'htmlifiedtext'.
1147 function recogniseContentType( data ) {
1148 if ( CKEDITOR.env.webkit ) {
1149 // Plain text or ( <div><br></div> and text inside <div> ).
1150 if ( !data.match( /^[^<]*$/g ) && !data.match( /^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi ) )
1151 return 'html';
1152 } else if ( CKEDITOR.env.ie ) {
1153 // Text and <br> or ( text and <br> in <p> - paragraphs can be separated by new \r\n ).
1154 if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) && !data.match( /^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi ) )
1155 return 'html';
1156 } else if ( CKEDITOR.env.gecko ) {
1157 // Text or <br>.
1158 if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) )
1159 return 'html';
1160 } else {
1161 return 'html';
1162 }
1163
1164 return 'htmlifiedtext';
1165 }
1166
1167 // This function transforms what browsers produce when
1168 // pasting plain text into editable element (see clipboard/paste.html TCs
1169 // for more info) into correct HTML (similar to that produced by text2Html).
1170 function htmlifiedTextHtmlification( config, data ) {
1171 function repeatParagraphs( repeats ) {
1172 // Repeat blocks floor((n+1)/2) times.
1173 // Even number of repeats - add <br> at the beginning of last <p>.
1174 return CKEDITOR.tools.repeat( '</p><p>', ~~( repeats / 2 ) ) + ( repeats % 2 == 1 ? '<br>' : '' );
1175 }
1176
1177 // Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
1178 data = data.replace( /\s+/g, ' ' )
1179 // Remove spaces from between tags.
1180 .replace( /> +</g, '><' )
1181 // Normalize XHTML syntax and upper cased <br> tags.
1182 .replace( /<br ?\/>/gi, '<br>' );
1183
1184 // IE - lower cased tags.
1185 data = data.replace( /<\/?[A-Z]+>/g, function( match ) {
1186 return match.toLowerCase();
1187 } );
1188
1189 // Don't touch single lines (no <br|p|div>) - nothing to do here.
1190 if ( data.match( /^[^<]$/ ) )
1191 return data;
1192
1193 // Webkit.
1194 if ( CKEDITOR.env.webkit && data.indexOf( '<div>' ) > -1 ) {
1195 // One line break at the beginning - insert <br>
1196 data = data.replace( /^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>' )
1197 // Two or more - reduce number of new lines by one.
1198 .replace( /^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>' );
1199
1200 // Two line breaks create one paragraph in Webkit.
1201 if ( data.match( /<div>(<br>|)<\/div>/ ) ) {
1202 data = '<p>' + data.replace( /(<div>(<br>|)<\/div>)+/g, function( match ) {
1203 return repeatParagraphs( match.split( '</div><div>' ).length + 1 );
1204 } ) + '</p>';
1205 }
1206
1207 // One line break create br.
1208 data = data.replace( /<\/div><div>/g, '<br>' );
1209
1210 // Remove remaining divs.
1211 data = data.replace( /<\/?div>/g, '' );
1212 }
1213
1214 // Opera and Firefox and enterMode != BR.
1215 if ( CKEDITOR.env.gecko && config.enterMode != CKEDITOR.ENTER_BR ) {
1216 // Remove bogus <br> - Fx generates two <brs> for one line break.
1217 // For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
1218 if ( CKEDITOR.env.gecko )
1219 data = data.replace( /^<br><br>$/, '<br>' );
1220
1221 // This line satisfy edge case when for Opera we have two line breaks
1222 //data = data.replace( /)
1223
1224 if ( data.indexOf( '<br><br>' ) > -1 ) {
1225 // Two line breaks create one paragraph, three - 2, four - 3, etc.
1226 data = '<p>' + data.replace( /(<br>){2,}/g, function( match ) {
1227 return repeatParagraphs( match.length / 4 );
1228 } ) + '</p>';
1229 }
1230 }
1231
1232 return switchEnterMode( config, data );
1233 }
1234
1235 function filtersFactoryFactory() {
1236 var filters = {};
1237
1238 function setUpTags() {
1239 var tags = {};
1240
1241 for ( var tag in CKEDITOR.dtd ) {
1242 if ( tag.charAt( 0 ) != '$' && tag != 'div' && tag != 'span' ) {
1243 tags[ tag ] = 1;
1244 }
1245 }
1246
1247 return tags;
1248 }
1249
1250 function createSemanticContentFilter() {
1251 var filter = new CKEDITOR.filter();
1252
1253 filter.allow( {
1254 $1: {
1255 elements: setUpTags(),
1256 attributes: true,
1257 styles: false,
1258 classes: false
1259 }
1260 } );
1261
1262 return filter;
1263 }
1264
1265 return {
1266 get: function( type ) {
1267 if ( type == 'plain-text' ) {
1268 // Does this look confusing to you? Did we forget about enter mode?
1269 // It is a trick that let's us creating one filter for edidtor, regardless of its
1270 // activeEnterMode (which as the name indicates can change during runtime).
1271 //
1272 // How does it work?
1273 // The active enter mode is passed to the filter.applyTo method.
1274 // The filter first marks all elements except <br> as disallowed and then tries to remove
1275 // them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
1276 // so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
1277 //
1278 // Now you can sleep well.
1279 return filters.plainText || ( filters.plainText = new CKEDITOR.filter( 'br' ) );
1280 } else if ( type == 'semantic-content' ) {
1281 return filters.semanticContent || ( filters.semanticContent = createSemanticContentFilter() );
1282 } else if ( type ) {
1283 // Create filter based on rules (string or object).
1284 return new CKEDITOR.filter( type );
1285 }
1286
1287 return null;
1288 }
1289 };
1290 }
1291
1292 function filterContent( editor, data, filter ) {
1293 var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data ),
1294 writer = new CKEDITOR.htmlParser.basicWriter();
1295
1296 filter.applyTo( fragment, true, false, editor.activeEnterMode );
1297 fragment.writeHtml( writer );
1298
1299 return writer.getHtml();
1300 }
1301
1302 function switchEnterMode( config, data ) {
1303 if ( config.enterMode == CKEDITOR.ENTER_BR ) {
1304 data = data.replace( /(<\/p><p>)+/g, function( match ) {
1305 return CKEDITOR.tools.repeat( '<br>', match.length / 7 * 2 );
1306 } ).replace( /<\/?p>/g, '' );
1307 } else if ( config.enterMode == CKEDITOR.ENTER_DIV ) {
1308 data = data.replace( /<(\/)?p>/g, '<$1div>' );
1309 }
1310
1311 return data;
1312 }
1313
1314 function preventDefaultSetDropEffectToNone( evt ) {
1315 evt.data.preventDefault();
1316 evt.data.$.dataTransfer.dropEffect = 'none';
1317 }
1318
1319 function initDragDrop( editor ) {
1320 var clipboard = CKEDITOR.plugins.clipboard;
1321
1322 editor.on( 'contentDom', function() {
1323 var editable = editor.editable(),
1324 dropTarget = CKEDITOR.plugins.clipboard.getDropTarget( editor ),
1325 top = editor.ui.space( 'top' ),
1326 bottom = editor.ui.space( 'bottom' );
1327
1328 // -------------- DRAGOVER TOP & BOTTOM --------------
1329
1330 // Not allowing dragging on toolbar and bottom (#12613).
1331 clipboard.preventDefaultDropOnElement( top );
1332 clipboard.preventDefaultDropOnElement( bottom );
1333
1334 // -------------- DRAGSTART --------------
1335 // Listed on dragstart to mark internal and cross-editor drag & drop
1336 // and save range and selected HTML.
1337
1338 editable.attachListener( dropTarget, 'dragstart', fireDragEvent );
1339
1340 // Make sure to reset data transfer (in case dragend was not called or was canceled).
1341 editable.attachListener( editor, 'dragstart', clipboard.resetDragDataTransfer, clipboard, null, 1 );
1342
1343 // Create a dataTransfer object and save it globally.
1344 editable.attachListener( editor, 'dragstart', function( evt ) {
1345 clipboard.initDragDataTransfer( evt, editor );
1346 }, null, null, 2 );
1347
1348 editable.attachListener( editor, 'dragstart', function() {
1349 // Save drag range globally for cross editor D&D.
1350 var dragRange = clipboard.dragRange = editor.getSelection().getRanges()[ 0 ];
1351
1352 // Store number of children, so we can later tell if any text node was split on drop. (#13011, #13447)
1353 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
1354 clipboard.dragStartContainerChildCount = dragRange ? getContainerChildCount( dragRange.startContainer ) : null;
1355 clipboard.dragEndContainerChildCount = dragRange ? getContainerChildCount( dragRange.endContainer ) : null;
1356 }
1357 }, null, null, 100 );
1358
1359 // -------------- DRAGEND --------------
1360 // Clean up on dragend.
1361
1362 editable.attachListener( dropTarget, 'dragend', fireDragEvent );
1363
1364 // Init data transfer if someone wants to use it in dragend.
1365 editable.attachListener( editor, 'dragend', clipboard.initDragDataTransfer, clipboard, null, 1 );
1366
1367 // When drag & drop is done we need to reset dataTransfer so the future
1368 // external drop will be not recognize as internal.
1369 editable.attachListener( editor, 'dragend', clipboard.resetDragDataTransfer, clipboard, null, 100 );
1370
1371 // -------------- DRAGOVER --------------
1372 // We need to call preventDefault on dragover because otherwise if
1373 // we drop image it will overwrite document.
1374
1375 editable.attachListener( dropTarget, 'dragover', function( evt ) {
1376 var target = evt.data.getTarget();
1377
1378 // Prevent reloading page when dragging image on empty document (#12619).
1379 if ( target && target.is && target.is( 'html' ) ) {
1380 evt.data.preventDefault();
1381 return;
1382 }
1383
1384 // If we do not prevent default dragover on IE the file path
1385 // will be loaded and we will lose content. On the other hand
1386 // if we prevent it the cursor will not we shown, so we prevent
1387 // dragover only on IE, on versions which support file API and only
1388 // if the event contains files.
1389 if ( CKEDITOR.env.ie &&
1390 CKEDITOR.plugins.clipboard.isFileApiSupported &&
1391 evt.data.$.dataTransfer.types.contains( 'Files' ) ) {
1392 evt.data.preventDefault();
1393 }
1394 } );
1395
1396 // -------------- DROP --------------
1397
1398 editable.attachListener( dropTarget, 'drop', function( evt ) {
1399 // Do nothing if event was already prevented. (#13879)
1400 if ( evt.data.$.defaultPrevented ) {
1401 return;
1402 }
1403
1404 // Cancel native drop.
1405 evt.data.preventDefault();
1406
1407 var target = evt.data.getTarget(),
1408 readOnly = target.isReadOnly();
1409
1410 // Do nothing if drop on non editable element (#13015).
1411 // 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).
1413 if ( readOnly && !( target.type == CKEDITOR.NODE_ELEMENT && target.is( 'html' ) ) ) {
1414 return;
1415 }
1416
1417 // Getting drop position is one of the most complex parts.
1418 var dropRange = clipboard.getRangeAtDropPosition( evt, editor ),
1419 dragRange = clipboard.dragRange;
1420
1421 // Do nothing if it was not possible to get drop range.
1422 if ( !dropRange ) {
1423 return;
1424 }
1425
1426 // Fire drop.
1427 fireDragEvent( evt, dragRange, dropRange );
1428 }, null, null, 9999 );
1429
1430 // Create dataTransfer or get it, if it was created before.
1431 editable.attachListener( editor, 'drop', clipboard.initDragDataTransfer, clipboard, null, 1 );
1432
1433 // Execute drop action, fire paste.
1434 editable.attachListener( editor, 'drop', function( evt ) {
1435 var data = evt.data;
1436
1437 if ( !data ) {
1438 return;
1439 }
1440
1441 // Let user modify drag and drop range.
1442 var dropRange = data.dropRange,
1443 dragRange = data.dragRange,
1444 dataTransfer = data.dataTransfer;
1445
1446 if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_INTERNAL ) {
1447 // Execute drop with a timeout because otherwise selection, after drop,
1448 // on IE is in the drag position, instead of drop position.
1449 setTimeout( function() {
1450 clipboard.internalDrop( dragRange, dropRange, dataTransfer, editor );
1451 }, 0 );
1452 } else if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
1453 crossEditorDrop( dragRange, dropRange, dataTransfer );
1454 } else {
1455 externalDrop( dropRange, dataTransfer );
1456 }
1457 }, null, null, 9999 );
1458
1459 // Cross editor drag and drop (drag in one Editor and drop in the other).
1460 function crossEditorDrop( dragRange, dropRange, dataTransfer ) {
1461 // Paste event should be fired before delete contents because otherwise
1462 // Chrome have a problem with drop range (Chrome split the drop
1463 // range container so the offset is bigger then container length).
1464 dropRange.select();
1465 firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );
1466
1467 // Remove dragged content and make a snapshot.
1468 dataTransfer.sourceEditor.fire( 'saveSnapshot' );
1469
1470 dataTransfer.sourceEditor.editable().extractHtmlFromRange( dragRange );
1471
1472 // Make some selection before saving snapshot, otherwise error will be thrown, because
1473 // there will be no valid selection after content is removed.
1474 dataTransfer.sourceEditor.getSelection().selectRanges( [ dragRange ] );
1475 dataTransfer.sourceEditor.fire( 'saveSnapshot' );
1476 }
1477
1478 // Drop from external source.
1479 function externalDrop( dropRange, dataTransfer ) {
1480 // Paste content into the drop position.
1481 dropRange.select();
1482
1483 firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );
1484
1485 // Usually we reset DataTranfer on dragend,
1486 // but dragend is called on the same element as dragstart
1487 // so it will not be called on on external drop.
1488 clipboard.resetDragDataTransfer();
1489 }
1490
1491 // Fire drag/drop events (dragstart, dragend, drop).
1492 function fireDragEvent( evt, dragRange, dropRange ) {
1493 var eventData = {
1494 $: evt.data.$,
1495 target: evt.data.getTarget()
1496 };
1497
1498 if ( dragRange ) {
1499 eventData.dragRange = dragRange;
1500 }
1501 if ( dropRange ) {
1502 eventData.dropRange = dropRange;
1503 }
1504
1505 if ( editor.fire( evt.name, eventData ) === false ) {
1506 evt.data.preventDefault();
1507 }
1508 }
1509
1510 function getContainerChildCount( container ) {
1511 if ( container.type != CKEDITOR.NODE_ELEMENT ) {
1512 container = container.getParent();
1513 }
1514
1515 return container.getChildCount();
1516 }
1517 } );
1518 }
1519
1520 /**
1521 * @singleton
1522 * @class CKEDITOR.plugins.clipboard
1523 */
1524 CKEDITOR.plugins.clipboard = {
1525 /**
1526 * True if the environment allows to set data on copy or cut manually. This value is false in IE, because this browser
1527 * shows the security dialog window when the script tries to set clipboard data and on iOS, because custom data is
1528 * not saved to clipboard there.
1529 *
1530 * @since 4.5
1531 * @readonly
1532 * @property {Boolean}
1533 */
1534 isCustomCopyCutSupported: !CKEDITOR.env.ie && !CKEDITOR.env.iOS,
1535
1536 /**
1537 * True if the environment supports MIME types and custom data types in dataTransfer/cliboardData getData/setData methods.
1538 *
1539 * @since 4.5
1540 * @readonly
1541 * @property {Boolean}
1542 */
1543 isCustomDataTypesSupported: !CKEDITOR.env.ie,
1544
1545 /**
1546 * True if the environment supports File API.
1547 *
1548 * @since 4.5
1549 * @readonly
1550 * @property {Boolean}
1551 */
1552 isFileApiSupported: !CKEDITOR.env.ie || CKEDITOR.env.version > 9,
1553
1554 /**
1555 * Main native paste event editable should listen to.
1556 *
1557 * **Note:** Safari does not like the {@link CKEDITOR.editor#beforePaste} event &mdash; it sometimes does not
1558 * handle <kbd>Ctrl+C</kbd> properly. This is probably caused by some race condition between events.
1559 * Chrome, Firefox and Edge work well with both events, so it is better to use {@link CKEDITOR.editor#paste}
1560 * which will handle pasting from e.g. browsers' menu bars.
1561 * IE7/8 does not like the {@link CKEDITOR.editor#paste} event for which it is throwing random errors.
1562 *
1563 * @since 4.5
1564 * @readonly
1565 * @property {String}
1566 */
1567 mainPasteEvent: ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) ? 'beforepaste' : 'paste',
1568
1569 /**
1570 * Returns `true` if it is expected that a browser provides HTML data through the Clipboard API.
1571 * If not, this method returns `false` and as a result CKEditor will use the paste bin. Read more in
1572 * the [Clipboard Integration](http://docs.ckeditor.com/#!/guide/dev_clipboard-section-clipboard-api) guide.
1573 *
1574 * @since 4.5.2
1575 * @returns {Boolean}
1576 */
1577 canClipboardApiBeTrusted: function( dataTransfer, editor ) {
1578 // If it's an internal or cross-editor data transfer, then it means that custom cut/copy/paste support works
1579 // and that the data were put manually on the data transfer so we can be sure that it's available.
1580 if ( dataTransfer.getTransferType( editor ) != CKEDITOR.DATA_TRANSFER_EXTERNAL ) {
1581 return true;
1582 }
1583
1584 // 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).
1586 if ( CKEDITOR.env.chrome && !dataTransfer.isEmpty() ) {
1587 return true;
1588 }
1589
1590 // 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).
1592 if ( CKEDITOR.env.gecko && ( dataTransfer.getData( 'text/html' ) || dataTransfer.getFilesCount() ) ) {
1593 return true;
1594 }
1595
1596 // In Safari and IE HTML data is not available though the Clipboard API.
1597 // 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
1599 // It is safer to use the paste bin in unknown cases.
1600 return false;
1601 },
1602
1603 /**
1604 * Returns the element that should be used as the target for the drop event.
1605 *
1606 * @since 4.5
1607 * @param {CKEDITOR.editor} editor The editor instance.
1608 * @returns {CKEDITOR.dom.domObject} the element that should be used as the target for the drop event.
1609 */
1610 getDropTarget: function( editor ) {
1611 var editable = editor.editable();
1612
1613 // #11123 Firefox needs to listen on document, because otherwise event won't be fired.
1614 // #11086 IE8 cannot listen on document.
1615 if ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ) {
1616 return editable;
1617 } else {
1618 return editor.document;
1619 }
1620 },
1621
1622 /**
1623 * IE 8 & 9 split text node on drop so the first node contains the
1624 * text before the drop position and the second contains the rest. If you
1625 * drag the content from the same node you will be not be able to get
1626 * it (the range becomes invalid), so you need to join them back.
1627 *
1628 * Note that the first node in IE 8 & 9 is the original node object
1629 * but with shortened content.
1630 *
1631 * Before:
1632 * --- Text Node A ----------------------------------
1633 * /\
1634 * Drag position
1635 *
1636 * After (IE 8 & 9):
1637 * --- Text Node A ----- --- Text Node B -----------
1638 * /\ /\
1639 * Drop position Drag position
1640 * (invalid)
1641 *
1642 * After (other browsers):
1643 * --- Text Node A ----------------------------------
1644 * /\ /\
1645 * Drop position Drag position
1646 *
1647 * **Note:** This function is in the public scope for tests usage only.
1648 *
1649 * @since 4.5
1650 * @private
1651 * @param {CKEDITOR.dom.range} dragRange The drag range.
1652 * @param {CKEDITOR.dom.range} dropRange The drop range.
1653 * @param {Number} preDragStartContainerChildCount The number of children of the drag range start container before the drop.
1654 * @param {Number} preDragEndContainerChildCount The number of children of the drag range end container before the drop.
1655 */
1656 fixSplitNodesAfterDrop: function( dragRange, dropRange, preDragStartContainerChildCount, preDragEndContainerChildCount ) {
1657 var dropContainer = dropRange.startContainer;
1658
1659 if (
1660 typeof preDragEndContainerChildCount != 'number' ||
1661 typeof preDragStartContainerChildCount != 'number'
1662 ) {
1663 return;
1664 }
1665
1666 // We are only concerned about ranges anchored in elements.
1667 if ( dropContainer.type != CKEDITOR.NODE_ELEMENT ) {
1668 return;
1669 }
1670
1671 if ( handleContainer( dragRange.startContainer, dropContainer, preDragStartContainerChildCount ) ) {
1672 return;
1673 }
1674
1675 if ( handleContainer( dragRange.endContainer, dropContainer, preDragEndContainerChildCount ) ) {
1676 return;
1677 }
1678
1679 function handleContainer( dragContainer, dropContainer, preChildCount ) {
1680 var dragElement = dragContainer;
1681 if ( dragElement.type == CKEDITOR.NODE_TEXT ) {
1682 dragElement = dragContainer.getParent();
1683 }
1684
1685 if ( dragElement.equals( dropContainer ) && preChildCount != dropContainer.getChildCount() ) {
1686 applyFix( dropRange );
1687 return true;
1688 }
1689 }
1690
1691 function applyFix( dropRange ) {
1692 var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ),
1693 nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset );
1694
1695 if (
1696 nodeBefore && nodeBefore.type == CKEDITOR.NODE_TEXT &&
1697 nodeAfter && nodeAfter.type == CKEDITOR.NODE_TEXT
1698 ) {
1699 var offset = nodeBefore.getLength();
1700
1701 nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() );
1702 nodeAfter.remove();
1703
1704 dropRange.setStart( nodeBefore, offset );
1705 dropRange.collapse( true );
1706 }
1707 }
1708 },
1709
1710 /**
1711 * Checks whether turning the drag range into bookmarks will invalidate the drop range.
1712 * This usually happens when the drop range shares the container with the drag range and is
1713 * located after the drag range, but there are countless edge cases.
1714 *
1715 * This function is stricly related to {@link #internalDrop} which toggles
1716 * order in which it creates bookmarks for both ranges based on a value returned
1717 * by this method. In some cases this method returns a value which is not necessarily
1718 * true in terms of what it was meant to check, but it is convenient, because
1719 * we know how it is interpreted in {@link #internalDrop}, so the correct
1720 * behavior of the entire algorithm is assured.
1721 *
1722 * **Note:** This function is in the public scope for tests usage only.
1723 *
1724 * @since 4.5
1725 * @private
1726 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
1727 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
1728 * @returns {Boolean} `true` if the first range is before the second range.
1729 */
1730 isDropRangeAffectedByDragRange: function( dragRange, dropRange ) {
1731 var dropContainer = dropRange.startContainer,
1732 dropOffset = dropRange.endOffset;
1733
1734 // Both containers are the same and drop offset is at the same position or later.
1735 // " A L] A " " M A "
1736 // ^ ^
1737 if ( dragRange.endContainer.equals( dropContainer ) && dragRange.endOffset <= dropOffset ) {
1738 return true;
1739 }
1740
1741 // Bookmark for drag start container will mess up with offsets.
1742 // " O [L A " " M A "
1743 // ^ ^
1744 if (
1745 dragRange.startContainer.getParent().equals( dropContainer ) &&
1746 dragRange.startContainer.getIndex() < dropOffset
1747 ) {
1748 return true;
1749 }
1750
1751 // Bookmark for drag end container will mess up with offsets.
1752 // " O] L A " " M A "
1753 // ^ ^
1754 if (
1755 dragRange.endContainer.getParent().equals( dropContainer ) &&
1756 dragRange.endContainer.getIndex() < dropOffset
1757 ) {
1758 return true;
1759 }
1760
1761 return false;
1762 },
1763
1764 /**
1765 * Internal drag and drop (drag and drop in the same editor instance).
1766 *
1767 * **Note:** This function is in the public scope for tests usage only.
1768 *
1769 * @since 4.5
1770 * @private
1771 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
1772 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
1773 * @param {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer
1774 * @param {CKEDITOR.editor} editor
1775 */
1776 internalDrop: function( dragRange, dropRange, dataTransfer, editor ) {
1777 var clipboard = CKEDITOR.plugins.clipboard,
1778 editable = editor.editable(),
1779 dragBookmark, dropBookmark, isDropRangeAffected;
1780
1781 // Save and lock snapshot so there will be only
1782 // one snapshot for both remove and insert content.
1783 editor.fire( 'saveSnapshot' );
1784 editor.fire( 'lockSnapshot', { dontUpdate: 1 } );
1785
1786 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
1787 this.fixSplitNodesAfterDrop(
1788 dragRange,
1789 dropRange,
1790 clipboard.dragStartContainerChildCount,
1791 clipboard.dragEndContainerChildCount
1792 );
1793 }
1794
1795 // Because we manipulate multiple ranges we need to do it carefully,
1796 // changing one range (event creating a bookmark) may make other invalid.
1797 // We need to change ranges into bookmarks so we can manipulate them easily in the future.
1798 // We can change the range which is later in the text before we change the preceding range.
1799 // We call isDropRangeAffectedByDragRange to test the order of ranges.
1800 isDropRangeAffected = this.isDropRangeAffectedByDragRange( dragRange, dropRange );
1801 if ( !isDropRangeAffected ) {
1802 dragBookmark = dragRange.createBookmark( false );
1803 }
1804 dropBookmark = dropRange.clone().createBookmark( false );
1805 if ( isDropRangeAffected ) {
1806 dragBookmark = dragRange.createBookmark( false );
1807 }
1808
1809 // Check if drop range is inside range.
1810 // 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.
1812 // When we drop, browser tries to find closest drop position and it finds it inside drag range. (#13453)
1813 var startNode = dragBookmark.startNode,
1814 endNode = dragBookmark.endNode,
1815 dropNode = dropBookmark.startNode,
1816 dropInsideDragRange =
1817 // Must check endNode because dragRange could be collapsed in some edge cases (simulated DnD).
1818 endNode &&
1819 ( startNode.getPosition( dropNode ) & CKEDITOR.POSITION_PRECEDING ) &&
1820 ( endNode.getPosition( dropNode ) & CKEDITOR.POSITION_FOLLOWING );
1821
1822 // If the drop range happens to be inside drag range change it's position to the beginning of the drag range.
1823 if ( dropInsideDragRange ) {
1824 // We only change position of bookmark span that is connected with dropBookmark.
1825 // dropRange will be overwritten and set to the dropBookmark later.
1826 dropNode.insertBefore( startNode );
1827 }
1828
1829 // No we can safely delete content for the drag range...
1830 dragRange = editor.createRange();
1831 dragRange.moveToBookmark( dragBookmark );
1832 editable.extractHtmlFromRange( dragRange, 1 );
1833
1834 // ...and paste content into the drop position.
1835 dropRange = editor.createRange();
1836 dropRange.moveToBookmark( dropBookmark );
1837
1838 // We do not select drop range, because of may be in the place we can not set the selection
1839 // (e.g. between blocks, in case of block widget D&D). We put range to the paste event instead.
1840 firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop', range: dropRange }, 1 );
1841
1842 editor.fire( 'unlockSnapshot' );
1843 },
1844
1845 /**
1846 * Gets the range from the `drop` event.
1847 *
1848 * @since 4.5
1849 * @param {Object} domEvent A native DOM drop event object.
1850 * @param {CKEDITOR.editor} editor The source editor instance.
1851 * @returns {CKEDITOR.dom.range} range at drop position.
1852 */
1853 getRangeAtDropPosition: function( dropEvt, editor ) {
1854 var $evt = dropEvt.data.$,
1855 x = $evt.clientX,
1856 y = $evt.clientY,
1857 $range,
1858 defaultRange = editor.getSelection( true ).getRanges()[ 0 ],
1859 range = editor.createRange();
1860
1861 // Make testing possible.
1862 if ( dropEvt.data.testRange )
1863 return dropEvt.data.testRange;
1864
1865 // Webkits.
1866 if ( document.caretRangeFromPoint ) {
1867 $range = editor.document.$.caretRangeFromPoint( x, y );
1868 range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
1869 range.collapse( true );
1870 }
1871 // FF.
1872 else if ( $evt.rangeParent ) {
1873 range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset );
1874 range.collapse( true );
1875 }
1876 // IEs 9+.
1877 // We check if editable is focused to make sure that it's an internal DnD. External DnD must use the second
1878 // mechanism because of http://dev.ckeditor.com/ticket/13472#comment:6.
1879 else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 && defaultRange && editor.editable().hasFocus ) {
1880 // On IE 9+ range by default is where we expected it.
1881 // defaultRange may be undefined if dragover was canceled (file drop).
1882 return defaultRange;
1883 }
1884 // IE 8 and all IEs if !defaultRange or external DnD.
1885 else if ( document.body.createTextRange ) {
1886 // To use this method we need a focus (which may be somewhere else in case of external drop).
1887 editor.focus();
1888
1889 $range = editor.document.getBody().$.createTextRange();
1890 try {
1891 var sucess = false;
1892
1893 // If user drop between text line IEs moveToPoint throws exception:
1894 //
1895 // Lorem ipsum pulvinar purus et euismod
1896 //
1897 // dolor sit amet,| consectetur adipiscing
1898 // *
1899 // vestibulum tincidunt augue eget tempus.
1900 //
1901 // * - drop position
1902 // | - expected cursor position
1903 //
1904 // So we try to call moveToPoint with +-1px up to +-20px above or
1905 // below original drop position to find nearest good drop position.
1906 for ( var i = 0; i < 20 && !sucess; i++ ) {
1907 if ( !sucess ) {
1908 try {
1909 $range.moveToPoint( x, y - i );
1910 sucess = true;
1911 } catch ( err ) {
1912 }
1913 }
1914 if ( !sucess ) {
1915 try {
1916 $range.moveToPoint( x, y + i );
1917 sucess = true;
1918 } catch ( err ) {
1919 }
1920 }
1921 }
1922
1923 if ( sucess ) {
1924 var id = 'cke-temp-' + ( new Date() ).getTime();
1925 $range.pasteHTML( '<span id="' + id + '">\u200b</span>' );
1926
1927 var span = editor.document.getById( id );
1928 range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START );
1929 span.remove();
1930 } else {
1931 // If the fist method does not succeed we might be next to
1932 // the short element (like header):
1933 //
1934 // Lorem ipsum pulvinar purus et euismod.
1935 //
1936 //
1937 // SOME HEADER| *
1938 //
1939 //
1940 // vestibulum tincidunt augue eget tempus.
1941 //
1942 // * - drop position
1943 // | - expected cursor position
1944 //
1945 // In such situation elementFromPoint returns proper element. Using getClientRect
1946 // it is possible to check if the cursor should be at the beginning or at the end
1947 // of paragraph.
1948 var $element = editor.document.$.elementFromPoint( x, y ),
1949 element = new CKEDITOR.dom.element( $element ),
1950 rect;
1951
1952 if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) {
1953 rect = element.getClientRect();
1954
1955 if ( x < rect.left ) {
1956 range.setStartAt( element, CKEDITOR.POSITION_AFTER_START );
1957 range.collapse( true );
1958 } else {
1959 range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END );
1960 range.collapse( true );
1961 }
1962 }
1963 // If drop happens on no element elementFromPoint returns html or body.
1964 //
1965 // * |Lorem ipsum pulvinar purus et euismod.
1966 //
1967 // vestibulum tincidunt augue eget tempus.
1968 //
1969 // * - drop position
1970 // | - expected cursor position
1971 //
1972 // In such case we can try to use default selection. If startContainer is not
1973 // 'editable' element it is probably proper selection.
1974 else if ( defaultRange && defaultRange.startContainer &&
1975 !defaultRange.startContainer.equals( editor.editable() ) ) {
1976 return defaultRange;
1977
1978 // Otherwise we can not find any drop position and we have to return null
1979 // and cancel drop event.
1980 } else {
1981 return null;
1982 }
1983
1984 }
1985 } catch ( err ) {
1986 return null;
1987 }
1988 } else {
1989 return null;
1990 }
1991
1992 return range;
1993 },
1994
1995 /**
1996 * This function tries to link the `evt.data.dataTransfer` property of the {@link CKEDITOR.editor#dragstart},
1997 * {@link CKEDITOR.editor#dragend} and {@link CKEDITOR.editor#drop} events to a single
1998 * {@link CKEDITOR.plugins.clipboard.dataTransfer} object.
1999 *
2000 * This method is automatically used by the core of the drag and drop functionality and
2001 * usually does not have to be called manually when using the drag and drop events.
2002 *
2003 * This method behaves differently depending on whether the drag and drop events were fired
2004 * artificially (to represent a non-native drag and drop) or whether they were caused by the native drag and drop.
2005 *
2006 * If the native event is not available, then it will create a new {@link CKEDITOR.plugins.clipboard.dataTransfer}
2007 * instance (if it does not exist already) and will link it to this and all following event objects until
2008 * the {@link #resetDragDataTransfer} method is called. It means that all three drag and drop events must be fired
2009 * in order to ensure that the data transfer is bound correctly.
2010 *
2011 * If the native event is available, then the {@link CKEDITOR.plugins.clipboard.dataTransfer} is identified
2012 * by its ID and a new instance is assigned to the `evt.data.dataTransfer` only if the ID changed or
2013 * the {@link #resetDragDataTransfer} method was called.
2014 *
2015 * @since 4.5
2016 * @param {CKEDITOR.dom.event} [evt] A drop event object.
2017 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
2018 */
2019 initDragDataTransfer: function( evt, sourceEditor ) {
2020 // Create a new dataTransfer object based on the drop event.
2021 // If this event was used on dragstart to create dataTransfer
2022 // both dataTransfer objects will have the same id.
2023 var nativeDataTransfer = evt.data.$ ? evt.data.$.dataTransfer : null,
2024 dataTransfer = new this.dataTransfer( nativeDataTransfer, sourceEditor );
2025
2026 if ( !nativeDataTransfer ) {
2027 // No native event.
2028 if ( this.dragData ) {
2029 dataTransfer = this.dragData;
2030 } else {
2031 this.dragData = dataTransfer;
2032 }
2033 } else {
2034 // Native event. If there is the same id we will replace dataTransfer with the one
2035 // created on drag, because it contains drag editor, drag content and so on.
2036 // Otherwise (in case of drag from external source) we save new object to
2037 // the global clipboard.dragData.
2038 if ( this.dragData && dataTransfer.id == this.dragData.id ) {
2039 dataTransfer = this.dragData;
2040 } else {
2041 this.dragData = dataTransfer;
2042 }
2043 }
2044
2045 evt.data.dataTransfer = dataTransfer;
2046 },
2047
2048 /**
2049 * Removes the global {@link #dragData} so the next call to {@link #initDragDataTransfer}
2050 * always creates a new instance of {@link CKEDITOR.plugins.clipboard.dataTransfer}.
2051 *
2052 * @since 4.5
2053 */
2054 resetDragDataTransfer: function() {
2055 this.dragData = null;
2056 },
2057
2058 /**
2059 * Global object storing the data transfer of the current drag and drop operation.
2060 * Do not use it directly, use {@link #initDragDataTransfer} and {@link #resetDragDataTransfer}.
2061 *
2062 * Note: This object is global (meaning that it is not related to a single editor instance)
2063 * in order to handle drag and drop from one editor into another.
2064 *
2065 * @since 4.5
2066 * @private
2067 * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData
2068 */
2069
2070 /**
2071 * Range object to save the drag range and remove its content after the drop.
2072 *
2073 * @since 4.5
2074 * @private
2075 * @property {CKEDITOR.dom.range} dragRange
2076 */
2077
2078 /**
2079 * Initializes and links data transfer objects based on the paste event. If the data
2080 * transfer object was already initialized on this event, the function will
2081 * return that object. In IE it is not possible to link copy/cut and paste events
2082 * so the method always returns a new object. The same happens if there is no paste event
2083 * passed to the method.
2084 *
2085 * @since 4.5
2086 * @param {CKEDITOR.dom.event} [evt] A paste event object.
2087 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
2088 * @returns {CKEDITOR.plugins.clipboard.dataTransfer} The data transfer object.
2089 */
2090 initPasteDataTransfer: function( evt, sourceEditor ) {
2091 if ( !this.isCustomCopyCutSupported ) {
2092 // Edge does not support custom copy/cut, but it have some useful data in the clipboardData (#13755).
2093 return new this.dataTransfer( ( CKEDITOR.env.edge && evt && evt.data.$ && evt.data.$.clipboardData ) || null, sourceEditor );
2094 } else if ( evt && evt.data && evt.data.$ ) {
2095 var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor );
2096
2097 if ( this.copyCutData && dataTransfer.id == this.copyCutData.id ) {
2098 dataTransfer = this.copyCutData;
2099 dataTransfer.$ = evt.data.$.clipboardData;
2100 } else {
2101 this.copyCutData = dataTransfer;
2102 }
2103
2104 return dataTransfer;
2105 } else {
2106 return new this.dataTransfer( null, sourceEditor );
2107 }
2108 },
2109
2110 /**
2111 * Prevents dropping on the specified element.
2112 *
2113 * @since 4.5
2114 * @param {CKEDITOR.dom.element} element The element on which dropping should be disabled.
2115 */
2116 preventDefaultDropOnElement: function( element ) {
2117 element && element.on( 'dragover', preventDefaultSetDropEffectToNone );
2118 }
2119 };
2120
2121 // Data type used to link drag and drop events.
2122 //
2123 // In IE URL data type is buggie and there is no way to mark drag & drop without
2124 // modifying text data (which would be displayed if user drop content to the textarea)
2125 // so we just read dragged text.
2126 //
2127 // In Chrome and Firefox we can use custom data types.
2128 var clipboardIdDataType = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ? 'cke/id' : 'Text';
2129 /**
2130 * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences
2131 * between browsers.
2132 *
2133 * @since 4.5
2134 * @class CKEDITOR.plugins.clipboard.dataTransfer
2135 * @constructor Creates a class instance.
2136 * @param {Object} [nativeDataTransfer] A native data transfer object.
2137 * @param {CKEDITOR.editor} [editor] The source editor instance. If the editor is defined, dataValue will
2138 * be created based on the editor content and the type will be 'html'.
2139 */
2140 CKEDITOR.plugins.clipboard.dataTransfer = function( nativeDataTransfer, editor ) {
2141 if ( nativeDataTransfer ) {
2142 this.$ = nativeDataTransfer;
2143 }
2144
2145 this._ = {
2146 metaRegExp: /^<meta.*?>/i,
2147 bodyRegExp: /<body(?:[\s\S]*?)>([\s\S]*)<\/body>/i,
2148 fragmentRegExp: /<!--(?:Start|End)Fragment-->/g,
2149
2150 data: {},
2151 files: [],
2152
2153 normalizeType: function( type ) {
2154 type = type.toLowerCase();
2155
2156 if ( type == 'text' || type == 'text/plain' ) {
2157 return 'Text'; // IE support only Text and URL;
2158 } else if ( type == 'url' ) {
2159 return 'URL'; // IE support only Text and URL;
2160 } else {
2161 return type;
2162 }
2163 }
2164 };
2165
2166 // Check if ID is already created.
2167 this.id = this.getData( clipboardIdDataType );
2168
2169 // If there is no ID we need to create it. Different browsers needs different ID.
2170 if ( !this.id ) {
2171 if ( clipboardIdDataType == 'Text' ) {
2172 // For IE10+ only Text data type is supported and we have to compare dragged
2173 // and dropped text. If the ID is not set it means that empty string was dragged
2174 // (ex. image with no alt). We change null to empty string.
2175 this.id = '';
2176 } else {
2177 // String for custom data type.
2178 this.id = 'cke-' + CKEDITOR.tools.getUniqueId();
2179 }
2180 }
2181
2182 // In IE10+ we can not use any data type besides text, so we do not call setData.
2183 if ( clipboardIdDataType != 'Text' ) {
2184 // Try to set ID so it will be passed from the drag to the drop event.
2185 // On some browsers with some event it is not possible to setData so we
2186 // need to catch exceptions.
2187 try {
2188 this.$.setData( clipboardIdDataType, this.id );
2189 } catch ( err ) {}
2190 }
2191
2192 if ( editor ) {
2193 this.sourceEditor = editor;
2194
2195 this.setData( 'text/html', editor.getSelectedHtml( 1 ) );
2196
2197 // Without setData( 'text', ... ) on dragstart there is no drop event in Safari.
2198 // Also 'text' data is empty as drop to the textarea does not work if we do not put there text.
2199 if ( clipboardIdDataType != 'Text' && !this.getData( 'text/plain' ) ) {
2200 this.setData( 'text/plain', editor.getSelection().getSelectedText() );
2201 }
2202 }
2203
2204 /**
2205 * Data transfer ID used to bind all dataTransfer
2206 * objects based on the same event (e.g. in drag and drop events).
2207 *
2208 * @readonly
2209 * @property {String} id
2210 */
2211
2212 /**
2213 * A native DOM event object.
2214 *
2215 * @readonly
2216 * @property {Object} $
2217 */
2218
2219 /**
2220 * Source editor &mdash; the editor where the drag starts.
2221 * Might be undefined if the drag starts outside the editor (e.g. when dropping files to the editor).
2222 *
2223 * @readonly
2224 * @property {CKEDITOR.editor} sourceEditor
2225 */
2226
2227 /**
2228 * Private properties and methods.
2229 *
2230 * @private
2231 * @property {Object} _
2232 */
2233 };
2234
2235 /**
2236 * Data transfer operation (drag and drop or copy and paste) started and ended in the same
2237 * editor instance.
2238 *
2239 * @since 4.5
2240 * @readonly
2241 * @property {Number} [=1]
2242 * @member CKEDITOR
2243 */
2244 CKEDITOR.DATA_TRANSFER_INTERNAL = 1;
2245
2246 /**
2247 * Data transfer operation (drag and drop or copy and paste) started in one editor
2248 * instance and ended in another.
2249 *
2250 * @since 4.5
2251 * @readonly
2252 * @property {Number} [=2]
2253 * @member CKEDITOR
2254 */
2255 CKEDITOR.DATA_TRANSFER_CROSS_EDITORS = 2;
2256
2257 /**
2258 * Data transfer operation (drag and drop or copy and paste) started outside of the editor.
2259 * The source of the data may be a textarea, HTML, another application, etc.
2260 *
2261 * @since 4.5
2262 * @readonly
2263 * @property {Number} [=3]
2264 * @member CKEDITOR
2265 */
2266 CKEDITOR.DATA_TRANSFER_EXTERNAL = 3;
2267
2268 CKEDITOR.plugins.clipboard.dataTransfer.prototype = {
2269 /**
2270 * Facade for the native `getData` method.
2271 *
2272 * @param {String} type The type of data to retrieve.
2273 * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist.
2274 */
2275 getData: function( type ) {
2276 function isEmpty( data ) {
2277 return data === undefined || data === null || data === '';
2278 }
2279
2280 type = this._.normalizeType( type );
2281
2282 var data = this._.data[ type ],
2283 result;
2284
2285 if ( isEmpty( data ) ) {
2286 try {
2287 data = this.$.getData( type );
2288 } catch ( e ) {}
2289 }
2290
2291 if ( isEmpty( data ) ) {
2292 data = '';
2293 }
2294
2295 // Some browsers add <meta http-equiv="content-type" content="text/html; charset=utf-8"> at the begging of the HTML data
2296 // or surround it with <html><head>...</head><body>(some content)<!--StartFragment--> and <!--EndFragment-->(some content)</body></html>
2297 // 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.
2299 //
2300 // See #13583 for more details.
2301 if ( type == 'text/html' ) {
2302 data = data.replace( this._.metaRegExp, '' );
2303
2304 // Keep only contents of the <body> element
2305 result = this._.bodyRegExp.exec( data );
2306 if ( result && result.length ) {
2307 data = result[ 1 ];
2308
2309 // Remove also comments.
2310 data = data.replace( this._.fragmentRegExp, '' );
2311 }
2312 }
2313 // Firefox on Linux put files paths as a text/plain data if there are files
2314 // in the dataTransfer object. We need to hide it, because files should be
2315 // handled on paste only if dataValue is empty.
2316 else if ( type == 'Text' && CKEDITOR.env.gecko && this.getFilesCount() &&
2317 data.substring( 0, 7 ) == 'file://' ) {
2318 data = '';
2319 }
2320
2321 return data;
2322 },
2323
2324 /**
2325 * Facade for the native `setData` method.
2326 *
2327 * @param {String} type The type of data to retrieve.
2328 * @param {String} value The data to add.
2329 */
2330 setData: function( type, value ) {
2331 type = this._.normalizeType( type );
2332
2333 this._.data[ type ] = value;
2334
2335 // There is "Unexpected call to method or property access." error if you try
2336 // to set data of unsupported type on IE.
2337 if ( !CKEDITOR.plugins.clipboard.isCustomDataTypesSupported && type != 'URL' && type != 'Text' ) {
2338 return;
2339 }
2340
2341 // 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.
2343 if ( clipboardIdDataType == 'Text' && type == 'Text' ) {
2344 this.id = value;
2345 }
2346
2347 try {
2348 this.$.setData( type, value );
2349 } catch ( e ) {}
2350 },
2351
2352 /**
2353 * Gets the data transfer type.
2354 *
2355 * @param {CKEDITOR.editor} targetEditor The drop/paste target editor instance.
2356 * @returns {Number} Possible values: {@link CKEDITOR#DATA_TRANSFER_INTERNAL},
2357 * {@link CKEDITOR#DATA_TRANSFER_CROSS_EDITORS}, {@link CKEDITOR#DATA_TRANSFER_EXTERNAL}.
2358 */
2359 getTransferType: function( targetEditor ) {
2360 if ( !this.sourceEditor ) {
2361 return CKEDITOR.DATA_TRANSFER_EXTERNAL;
2362 } else if ( this.sourceEditor == targetEditor ) {
2363 return CKEDITOR.DATA_TRANSFER_INTERNAL;
2364 } else {
2365 return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS;
2366 }
2367 },
2368
2369 /**
2370 * Copies the data from the native data transfer to a private cache.
2371 * This function is needed because the data from the native data transfer
2372 * is available only synchronously to the event listener. It is not possible
2373 * to get the data asynchronously, after a timeout, and the {@link CKEDITOR.editor#paste}
2374 * event is fired asynchronously &mdash; hence the need for caching the data.
2375 */
2376 cacheData: function() {
2377 if ( !this.$ ) {
2378 return;
2379 }
2380
2381 var that = this,
2382 i, file;
2383
2384 function getAndSetData( type ) {
2385 type = that._.normalizeType( type );
2386
2387 var data = that.getData( type );
2388 if ( data ) {
2389 that._.data[ type ] = data;
2390 }
2391 }
2392
2393 // Copy data.
2394 if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
2395 if ( this.$.types ) {
2396 for ( i = 0; i < this.$.types.length; i++ ) {
2397 getAndSetData( this.$.types[ i ] );
2398 }
2399 }
2400 } else {
2401 getAndSetData( 'Text' );
2402 getAndSetData( 'URL' );
2403 }
2404
2405 // Copy files references.
2406 file = this._getImageFromClipboard();
2407 if ( ( this.$ && this.$.files ) || file ) {
2408 this._.files = [];
2409
2410 // Edge have empty files property with no length (#13755).
2411 if ( this.$.files && this.$.files.length ) {
2412 for ( i = 0; i < this.$.files.length; i++ ) {
2413 this._.files.push( this.$.files[ i ] );
2414 }
2415 }
2416
2417 // Don't include $.items if both $.files and $.items contains files, because,
2418 // according to spec and browsers behavior, they contain the same files.
2419 if ( this._.files.length === 0 && file ) {
2420 this._.files.push( file );
2421 }
2422 }
2423 },
2424
2425 /**
2426 * Gets the number of files in the dataTransfer object.
2427 *
2428 * @returns {Number} The number of files.
2429 */
2430 getFilesCount: function() {
2431 if ( this._.files.length ) {
2432 return this._.files.length;
2433 }
2434
2435 if ( this.$ && this.$.files && this.$.files.length ) {
2436 return this.$.files.length;
2437 }
2438
2439 return this._getImageFromClipboard() ? 1 : 0;
2440 },
2441
2442 /**
2443 * Gets the file at the index given.
2444 *
2445 * @param {Number} i Index.
2446 * @returns {File} File instance.
2447 */
2448 getFile: function( i ) {
2449 if ( this._.files.length ) {
2450 return this._.files[ i ];
2451 }
2452
2453 if ( this.$ && this.$.files && this.$.files.length ) {
2454 return this.$.files[ i ];
2455 }
2456
2457 // File or null if the file was not found.
2458 return i === 0 ? this._getImageFromClipboard() : undefined;
2459 },
2460
2461 /**
2462 * Checks if the data transfer contains any data.
2463 *
2464 * @returns {Boolean} `true` if the object contains no data.
2465 */
2466 isEmpty: function() {
2467 var typesToCheck = {},
2468 type;
2469
2470 // If dataTransfer contains files it is not empty.
2471 if ( this.getFilesCount() ) {
2472 return false;
2473 }
2474
2475 // Add custom types.
2476 for ( type in this._.data ) {
2477 typesToCheck[ type ] = 1;
2478 }
2479
2480 // Add native types.
2481 if ( this.$ ) {
2482 if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
2483 if ( this.$.types ) {
2484 for ( var i = 0; i < this.$.types.length; i++ ) {
2485 typesToCheck[ this.$.types[ i ] ] = 1;
2486 }
2487 }
2488 } else {
2489 typesToCheck.Text = 1;
2490 typesToCheck.URL = 1;
2491 }
2492 }
2493
2494 // Remove ID.
2495 if ( clipboardIdDataType != 'Text' ) {
2496 typesToCheck[ clipboardIdDataType ] = 0;
2497 }
2498
2499 for ( type in typesToCheck ) {
2500 if ( typesToCheck[ type ] && this.getData( type ) !== '' ) {
2501 return false;
2502 }
2503 }
2504
2505 return true;
2506 },
2507
2508 /**
2509 * 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).
2511 *
2512 * @private
2513 * @returns {File} File instance or `null` if not found.
2514 */
2515 _getImageFromClipboard: function() {
2516 var file;
2517
2518 if ( this.$ && this.$.items && this.$.items[ 0 ] ) {
2519 try {
2520 file = this.$.items[ 0 ].getAsFile();
2521 // Duck typing
2522 if ( file && file.type ) {
2523 return file;
2524 }
2525 } catch ( err ) {
2526 // noop
2527 }
2528 }
2529
2530 return undefined;
2531 }
2532 };
2533 } )();
2534
2535 /**
2536 * The default content type that is used when pasted data cannot be clearly recognized as HTML or text.
2537 *
2538 * For example: `'foo'` may come from a plain text editor or a website. It is not possible to recognize the content
2539 * type in this case, so the default type will be used. At the same time it is clear that `'<b>example</b> text'` is
2540 * HTML and its origin is a web page, email or another rich text editor.
2541 *
2542 * **Note:** If content type is text, then styles of the paste context are preserved.
2543 *
2544 * CKEDITOR.config.clipboard_defaultContentType = 'text';
2545 *
2546 * See also the {@link CKEDITOR.editor#paste} event and read more about the integration with clipboard
2547 * in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2548 *
2549 * @since 4.0
2550 * @cfg {'html'/'text'} [clipboard_defaultContentType='html']
2551 * @member CKEDITOR.config
2552 */
2553
2554 /**
2555 * Fired after the user initiated a paste action, but before the data is inserted into the editor.
2556 * The listeners to this event are able to process the content before its insertion into the document.
2557 *
2558 * Read more about the integration with clipboard in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2559 *
2560 * See also:
2561 *
2562 * * the {@link CKEDITOR.config#pasteFilter} option,
2563 * * the {@link CKEDITOR.editor#drop} event,
2564 * * the {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2565 *
2566 * @since 3.1
2567 * @event paste
2568 * @member CKEDITOR.editor
2569 * @param {CKEDITOR.editor} editor This editor instance.
2570 * @param data
2571 * @param {String} data.type The type of data in `data.dataValue`. Usually `'html'` or `'text'`, but for listeners
2572 * with a priority smaller than `6` it may also be `'auto'` which means that the content type has not been recognised yet
2573 * (this will be done by the content type sniffer that listens with priority `6`).
2574 * @param {String} data.dataValue HTML to be pasted.
2575 * @param {String} data.method Indicates the data transfer method. It could be drag and drop or copy and paste.
2576 * Possible values: `'drop'`, `'paste'`. Introduced in CKEditor 4.5.
2577 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer Facade for the native dataTransfer object
2578 * which provides access to various data types and files, and passes some data between linked events
2579 * (like drag and drop). Introduced in CKEditor 4.5.
2580 * @param {Boolean} [data.dontFilter=false] Whether the {@link CKEDITOR.editor#pasteFilter paste filter} should not
2581 * be applied to data. This option has no effect when `data.type` equals `'text'` which means that for instance
2582 * {@link CKEDITOR.config#forcePasteAsPlainText} has a higher priority. Introduced in CKEditor 4.5.
2583 */
2584
2585 /**
2586 * Fired before the {@link #paste} event. Allows to preset data type.
2587 *
2588 * **Note:** This event is deprecated. Add a `0` priority listener for the
2589 * {@link #paste} event instead.
2590 *
2591 * @deprecated
2592 * @event beforePaste
2593 * @member CKEDITOR.editor
2594 */
2595
2596 /**
2597 * 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.
2599 *
2600 * @event afterPaste
2601 * @member CKEDITOR.editor
2602 */
2603
2604 /**
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.
2616 *
2617 * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event.
2618 * Use the `drop` event only to control drag and drop operations (e.g. to prevent the ability to drop some content).
2619 *
2620 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2621 *
2622 * See also:
2623 *
2624 * * The {@link CKEDITOR.editor#paste} event,
2625 * * The {@link CKEDITOR.editor#dragstart} and {@link CKEDITOR.editor#dragend} events,
2626 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2627 *
2628 * @since 4.5
2629 * @event drop
2630 * @member CKEDITOR.editor
2631 * @param {CKEDITOR.editor} editor This editor instance.
2632 * @param data
2633 * @param {Object} data.$ Native drop event.
2634 * @param {CKEDITOR.dom.node} data.target Drop target.
2635 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2636 * @param {CKEDITOR.dom.range} data.dragRange Drag range, lets you manipulate the drag range.
2637 * Note that dragged HTML is saved as `text/html` data on `dragstart` so if you change the drag range
2638 * on drop, dropped HTML will not change. You need to change it manually using
2639 * {@link CKEDITOR.plugins.clipboard.dataTransfer#setData dataTransfer.setData}.
2640 * @param {CKEDITOR.dom.range} data.dropRange Drop range, lets you manipulate the drop range.
2641 */
2642
2643 /**
2644 * Facade for the native `dragstart` event. Fired when the native `dragstart` event occurs.
2645 *
2646 * 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
2647 * operation. For instance, the `widget` plugin uses this option to integrate its custom block widget drag and drop with
2648 * the entire system.
2649 *
2650 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2651 *
2652 * See also:
2653 *
2654 * * The {@link CKEDITOR.editor#paste} event,
2655 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
2656 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2657 *
2658 * @since 4.5
2659 * @event dragstart
2660 * @member CKEDITOR.editor
2661 * @param {CKEDITOR.editor} editor This editor instance.
2662 * @param data
2663 * @param {Object} data.$ Native dragstart event.
2664 * @param {CKEDITOR.dom.node} data.target Drag target.
2665 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2666 */
2667
2668 /**
2669 * Facade for the native `dragend` event. Fired when the native `dragend` event occurs.
2670 *
2671 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
2672 *
2673 * See also:
2674 *
2675 * * The {@link CKEDITOR.editor#paste} event,
2676 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
2677 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
2678 *
2679 * @since 4.5
2680 * @event dragend
2681 * @member CKEDITOR.editor
2682 * @param {CKEDITOR.editor} editor This editor instance.
2683 * @param data
2684 * @param {Object} data.$ Native dragend event.
2685 * @param {CKEDITOR.dom.node} data.target Drag target.
2686 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
2687 */
2688
2689 /**
2690 * Defines a filter which is applied to external data pasted or dropped into the editor. Possible values are:
2691 *
2692 * * `'plain-text'` &ndash; Content will be pasted as a plain text.
2693 * * `'semantic-content'` &ndash; Known tags (except `div`, `span`) with all attributes (except
2694 * `style` and `class`) will be kept.
2695 * * `'h1 h2 p div'` &ndash; Custom rules compatible with {@link CKEDITOR.filter}.
2696 * * `null` &ndash; Content will not be filtered by the paste filter (but it still may be filtered
2697 * by [Advanced Content Filter](#!/guide/dev_advanced_content_filter)). This value can be used to
2698 * disable the paste filter in Chrome and Safari, where this option defaults to `'semantic-content'`.
2699 *
2700 * Example:
2701 *
2702 * config.pasteFilter = 'plain-text';
2703 *
2704 * Custom setting:
2705 *
2706 * config.pasteFilter = 'h1 h2 p ul ol li; img[!src, alt]; a[!href]';
2707 *
2708 * Based on this configuration option, a proper {@link CKEDITOR.filter} instance will be defined and assigned to the editor
2709 * as a {@link CKEDITOR.editor#pasteFilter}. You can tweak the paste filter settings on the fly on this object
2710 * as well as delete or replace it.
2711 *
2712 * var editor = CKEDITOR.replace( 'editor', {
2713 * pasteFilter: 'semantic-content'
2714 * } );
2715 *
2716 * editor.on( 'instanceReady', function() {
2717 * // The result of this will be that all semantic content will be preserved
2718 * // except tables.
2719 * editor.pasteFilter.disallow( 'table' );
2720 * } );
2721 *
2722 * Note that the paste filter is applied only to **external** data. There are three data sources:
2723 *
2724 * * copied and pasted in the same editor (internal),
2725 * * copied from one editor and pasted into another (cross-editor),
2726 * * coming from all other sources like websites, MS Word, etc. (external).
2727 *
2728 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
2729 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
2730 * external data which often needs to be handled differently than content produced by the editor.
2731 *
2732 * This setting defaults to `'semantic-content'` in Chrome, Opera and Safari (all Blink and Webkit based browsers)
2733 * due to messy HTML which these browsers keep in the clipboard. In other browsers it defaults to `null`.
2734 *
2735 * @since 4.5
2736 * @cfg {String} [pasteFilter='semantic-content' in Chrome and Safari and `null` in other browsers]
2737 * @member CKEDITOR.config
2738 */
2739
2740 /**
2741 * {@link CKEDITOR.filter Content filter} which is used when external data is pasted or dropped into the editor
2742 * or a forced paste as plain text occurs.
2743 *
2744 * This object might be used on the fly to define rules for pasted external content.
2745 * This object is available and used if the {@link CKEDITOR.plugins.clipboard clipboard} plugin is enabled and
2746 * {@link CKEDITOR.config#pasteFilter} or {@link CKEDITOR.config#forcePasteAsPlainText} was defined.
2747 *
2748 * To enable the filter:
2749 *
2750 * var editor = CKEDITOR.replace( 'editor', {
2751 * pasteFilter: 'plain-text'
2752 * } );
2753 *
2754 * You can also modify the filter on the fly later on:
2755 *
2756 * editor.pasteFilter = new CKEDITOR.filter( 'p h1 h2; a[!href]' );
2757 *
2758 * Note that the paste filter is only applied to **external** data. There are three data sources:
2759 *
2760 * * copied and pasted in the same editor (internal),
2761 * * copied from one editor and pasted into another (cross-editor),
2762 * * coming from all other sources like websites, MS Word, etc. (external).
2763 *
2764 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
2765 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
2766 * external data which often needs to be handled differently than content produced by the editor.
2767 *
2768 * @since 4.5
2769 * @readonly
2770 * @property {CKEDITOR.filter} [pasteFilter]
2771 * @member CKEDITOR.editor
2772 */