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