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