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