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