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