JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
9dc230ebd1e0ff65f43078532fe353dd4b7334a9
[ckeditor.git] / _source / plugins / clipboard / plugin.js
1 /*\r
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 /**\r
7  * @file Clipboard support\r
8  */\r
9 \r
10 (function()\r
11 {\r
12         // Tries to execute any of the paste, cut or copy commands in IE. Returns a\r
13         // boolean indicating that the operation succeeded.\r
14         var execIECommand = function( editor, command )\r
15         {\r
16                 var doc = editor.document,\r
17                         body = doc.getBody();\r
18 \r
19                 var     enabled = false;\r
20                 var onExec = function()\r
21                 {\r
22                         enabled = true;\r
23                 };\r
24 \r
25                 // The following seems to be the only reliable way to detect that\r
26                 // clipboard commands are enabled in IE. It will fire the\r
27                 // onpaste/oncut/oncopy events only if the security settings allowed\r
28                 // the command to execute.\r
29                 body.on( command, onExec );\r
30 \r
31                 // IE6/7: document.execCommand has problem to paste into positioned element.\r
32                 ( CKEDITOR.env.version > 7 ? doc.$ : doc.$.selection.createRange() ) [ 'execCommand' ]( command );\r
33 \r
34                 body.removeListener( command, onExec );\r
35 \r
36                 return enabled;\r
37         };\r
38 \r
39         // Attempts to execute the Cut and Copy operations.\r
40         var tryToCutCopy =\r
41                 CKEDITOR.env.ie ?\r
42                         function( editor, type )\r
43                         {\r
44                                 return execIECommand( editor, type );\r
45                         }\r
46                 :               // !IE.\r
47                         function( editor, type )\r
48                         {\r
49                                 try\r
50                                 {\r
51                                         // Other browsers throw an error if the command is disabled.\r
52                                         return editor.document.$.execCommand( type );\r
53                                 }\r
54                                 catch( e )\r
55                                 {\r
56                                         return false;\r
57                                 }\r
58                         };\r
59 \r
60         // A class that represents one of the cut or copy commands.\r
61         var cutCopyCmd = function( type )\r
62         {\r
63                 this.type = type;\r
64                 this.canUndo = ( this.type == 'cut' );          // We can't undo copy to clipboard.\r
65         };\r
66 \r
67         cutCopyCmd.prototype =\r
68         {\r
69                 exec : function( editor, data )\r
70                 {\r
71                         this.type == 'cut' && fixCut( editor );\r
72 \r
73                         var success = tryToCutCopy( editor, this.type );\r
74 \r
75                         if ( !success )\r
76                                 alert( editor.lang.clipboard[ this.type + 'Error' ] );          // Show cutError or copyError.\r
77 \r
78                         return success;\r
79                 }\r
80         };\r
81 \r
82         // Paste command.\r
83         var pasteCmd =\r
84         {\r
85                 canUndo : false,\r
86 \r
87                 exec :\r
88                         CKEDITOR.env.ie ?\r
89                                 function( editor )\r
90                                 {\r
91                                         // Prevent IE from pasting at the begining of the document.\r
92                                         editor.focus();\r
93 \r
94                                         if ( !editor.document.getBody().fire( 'beforepaste' )\r
95                                                  && !execIECommand( editor, 'paste' ) )\r
96                                         {\r
97                                                 editor.fire( 'pasteDialog' );\r
98                                                 return false;\r
99                                         }\r
100                                 }\r
101                         :\r
102                                 function( editor )\r
103                                 {\r
104                                         try\r
105                                         {\r
106                                                 if ( !editor.document.getBody().fire( 'beforepaste' )\r
107                                                          && !editor.document.$.execCommand( 'Paste', false, null ) )\r
108                                                 {\r
109                                                         throw 0;\r
110                                                 }\r
111                                         }\r
112                                         catch ( e )\r
113                                         {\r
114                                                 setTimeout( function()\r
115                                                         {\r
116                                                                 editor.fire( 'pasteDialog' );\r
117                                                         }, 0 );\r
118                                                 return false;\r
119                                         }\r
120                                 }\r
121         };\r
122 \r
123         // Listens for some clipboard related keystrokes, so they get customized.\r
124         var onKey = function( event )\r
125         {\r
126                 if ( this.mode != 'wysiwyg' )\r
127                         return;\r
128 \r
129                 switch ( event.data.keyCode )\r
130                 {\r
131                         // Paste\r
132                         case CKEDITOR.CTRL + 86 :               // CTRL+V\r
133                         case CKEDITOR.SHIFT + 45 :              // SHIFT+INS\r
134 \r
135                                 var body = this.document.getBody();\r
136 \r
137                                 // Simulate 'beforepaste' event for all none-IEs.\r
138                                 if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) )\r
139                                         event.cancel();\r
140                                 // Simulate 'paste' event for Opera/Firefox2.\r
141                                 else if ( CKEDITOR.env.opera\r
142                                                  || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )\r
143                                         body.fire( 'paste' );\r
144                                 return;\r
145 \r
146                         // Cut\r
147                         case CKEDITOR.CTRL + 88 :               // CTRL+X\r
148                         case CKEDITOR.SHIFT + 46 :              // SHIFT+DEL\r
149 \r
150                                 // Save Undo snapshot.\r
151                                 var editor = this;\r
152                                 this.fire( 'saveSnapshot' );            // Save before paste\r
153                                 setTimeout( function()\r
154                                         {\r
155                                                 editor.fire( 'saveSnapshot' );          // Save after paste\r
156                                         }, 0 );\r
157                 }\r
158         };\r
159 \r
160         // Allow to peek clipboard content by redirecting the\r
161         // pasting content into a temporary bin and grab the content of it.\r
162         function getClipboardData( evt, mode, callback )\r
163         {\r
164                 var doc = this.document;\r
165 \r
166                 // Avoid recursions on 'paste' event for IE.\r
167                 if ( CKEDITOR.env.ie && doc.getById( 'cke_pastebin' ) )\r
168                         return;\r
169 \r
170                 // If the browser supports it, get the data directly\r
171                 if (mode == 'text' && evt.data && evt.data.$.clipboardData)\r
172                 {\r
173                         // evt.data.$.clipboardData.types contains all the flavours in Mac's Safari, but not on windows.\r
174                         var plain = evt.data.$.clipboardData.getData( 'text/plain' );\r
175                         if (plain)\r
176                         {\r
177                                 evt.data.preventDefault();\r
178                                 callback( plain );\r
179                                 return;\r
180                         }\r
181                 }\r
182 \r
183                 var sel = this.getSelection(),\r
184                         range = new CKEDITOR.dom.range( doc );\r
185 \r
186                 // Create container to paste into\r
187                 var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : CKEDITOR.env.webkit ? 'body' : 'div', doc );\r
188                 pastebin.setAttribute( 'id', 'cke_pastebin' );\r
189                 // Safari requires a filler node inside the div to have the content pasted into it. (#4882)\r
190                 CKEDITOR.env.webkit && pastebin.append( doc.createText( '\xa0' ) );\r
191                 doc.getBody().append( pastebin );\r
192 \r
193                 pastebin.setStyles(\r
194                         {\r
195                                 position : 'absolute',\r
196                                 // Position the bin exactly at the position of the selected element\r
197                                 // to avoid any subsequent document scroll.\r
198                                 top : sel.getStartElement().getDocumentPosition().y + 'px',\r
199                                 width : '1px',\r
200                                 height : '1px',\r
201                                 overflow : 'hidden'\r
202                         });\r
203 \r
204                 // It's definitely a better user experience if we make the paste-bin pretty unnoticed\r
205                 // by pulling it off the screen.\r
206                 pastebin.setStyle( this.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' );\r
207 \r
208                 var bms = sel.createBookmarks();\r
209 \r
210                 // Turn off design mode temporarily before give focus to the paste bin.\r
211                 if ( mode == 'text' )\r
212                 {\r
213                         if ( CKEDITOR.env.ie )\r
214                         {\r
215                                 var ieRange = doc.getBody().$.createTextRange();\r
216                                 ieRange.moveToElementText( pastebin.$ );\r
217                                 ieRange.execCommand( 'Paste' );\r
218                                 evt.data.preventDefault();\r
219                         }\r
220                         else\r
221                         {\r
222                                 doc.$.designMode = 'off';\r
223                                 pastebin.$.focus();\r
224                         }\r
225                 }\r
226                 else\r
227                 {\r
228                         range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START );\r
229                         range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END );\r
230                         range.select( true );\r
231                 }\r
232 \r
233                 // Wait a while and grab the pasted contents\r
234                 window.setTimeout( function()\r
235                 {\r
236                         mode == 'text' && !CKEDITOR.env.ie && ( doc.$.designMode = 'on' );\r
237                         pastebin.remove();\r
238 \r
239                         // Grab the HTML contents.\r
240                         // We need to look for a apple style wrapper on webkit it also adds\r
241                         // a div wrapper if you copy/paste the body of the editor.\r
242                         // Remove hidden div and restore selection.\r
243                         var bogusSpan;\r
244                         pastebin = ( CKEDITOR.env.webkit\r
245                                                  && ( bogusSpan = pastebin.getFirst() )\r
246                                                  && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ?\r
247                                                         bogusSpan : pastebin );\r
248 \r
249                         sel.selectBookmarks( bms );\r
250                         callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() );\r
251                 }, 0 );\r
252         }\r
253 \r
254         // Cutting off control type element in IE standards breaks the selection entirely. (#4881)\r
255         function fixCut( editor )\r
256         {\r
257                 if ( !CKEDITOR.env.ie || editor.document.$.compatMode == 'BackCompat' )\r
258                         return;\r
259 \r
260                 var sel = editor.getSelection();\r
261                 var control;\r
262                 if( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) )\r
263                 {\r
264                         var range = sel.getRanges()[ 0 ];\r
265                         var dummy = editor.document.createText( '' );\r
266                         dummy.insertBefore( control );\r
267                         range.setStartBefore( dummy );\r
268                         range.setEndAfter( control );\r
269                         sel.selectRanges( [ range ] );\r
270 \r
271                         // Clear up the fix if the paste wasn't succeeded.\r
272                         setTimeout( function()\r
273                         {\r
274                                 // Element still online?\r
275                                 if ( control.getParent() )\r
276                                 {\r
277                                         dummy.remove();\r
278                                         sel.selectElement( control );\r
279                                 }\r
280                         }, 0 );\r
281                 }\r
282         }\r
283 \r
284         // Register the plugin.\r
285         CKEDITOR.plugins.add( 'clipboard',\r
286                 {\r
287                         requires : [ 'dialog', 'htmldataprocessor' ],\r
288                         init : function( editor )\r
289                         {\r
290                                 // Inserts processed data into the editor at the end of the\r
291                                 // events chain.\r
292                                 editor.on( 'paste', function( evt )\r
293                                         {\r
294                                                 var data = evt.data;\r
295                                                 if ( data[ 'html' ] )\r
296                                                         editor.insertHtml( data[ 'html' ] );\r
297                                                 else if ( data[ 'text' ] )\r
298                                                         editor.insertText( data[ 'text' ] );\r
299 \r
300                                         }, null, null, 1000 );\r
301 \r
302                                 editor.on( 'pasteDialog', function( evt )\r
303                                         {\r
304                                                 setTimeout( function()\r
305                                                 {\r
306                                                         // Open default paste dialog.\r
307                                                         editor.openDialog( 'paste' );\r
308                                                 }, 0 );\r
309                                         });\r
310 \r
311                                 function addButtonCommand( buttonName, commandName, command, ctxMenuOrder )\r
312                                 {\r
313                                         var lang = editor.lang[ commandName ];\r
314 \r
315                                         editor.addCommand( commandName, command );\r
316                                         editor.ui.addButton( buttonName,\r
317                                                 {\r
318                                                         label : lang,\r
319                                                         command : commandName\r
320                                                 });\r
321 \r
322                                         // If the "menu" plugin is loaded, register the menu item.\r
323                                         if ( editor.addMenuItems )\r
324                                         {\r
325                                                 editor.addMenuItem( commandName,\r
326                                                         {\r
327                                                                 label : lang,\r
328                                                                 command : commandName,\r
329                                                                 group : 'clipboard',\r
330                                                                 order : ctxMenuOrder\r
331                                                         });\r
332                                         }\r
333                                 }\r
334 \r
335                                 addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 );\r
336                                 addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 );\r
337                                 addButtonCommand( 'Paste', 'paste', pasteCmd, 8 );\r
338 \r
339                                 CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );\r
340 \r
341                                 editor.on( 'key', onKey, editor );\r
342 \r
343                                 var mode = editor.config.forcePasteAsPlainText ? 'text' : 'html';\r
344 \r
345                                 // We'll be catching all pasted content in one line, regardless of whether the\r
346                                 // it's introduced by a document command execution (e.g. toolbar buttons) or\r
347                                 // user paste behaviors. (e.g. Ctrl-V)\r
348                                 editor.on( 'contentDom', function()\r
349                                 {\r
350                                         var body = editor.document.getBody();\r
351                                         body.on( ( (mode == 'text' && CKEDITOR.env.ie) || CKEDITOR.env.webkit ) ? 'paste' : 'beforepaste',\r
352                                                 function( evt )\r
353                                                 {\r
354                                                         if ( depressBeforeEvent )\r
355                                                                 return;\r
356 \r
357                                                         getClipboardData.call( editor, evt, mode, function ( data )\r
358                                                         {\r
359                                                                 // The very last guard to make sure the\r
360                                                                 // paste has successfully happened.\r
361                                                                 if ( !data )\r
362                                                                         return;\r
363 \r
364                                                                 var dataTransfer = {};\r
365                                                                 dataTransfer[ mode ] = data;\r
366                                                                 editor.fire( 'paste', dataTransfer );\r
367                                                         } );\r
368                                                 });\r
369 \r
370                                         body.on( 'beforecut', function() { !depressBeforeEvent && fixCut( editor ); } );\r
371                                 });\r
372 \r
373                                 // If the "contextmenu" plugin is loaded, register the listeners.\r
374                                 if ( editor.contextMenu )\r
375                                 {\r
376                                         var depressBeforeEvent;\r
377                                         function stateFromNamedCommand( command )\r
378                                         {\r
379                                                 // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)',\r
380                                                 // guard to distinguish from the ordinary sources( either\r
381                                                 // keyboard paste or execCommand ) (#4874).\r
382                                                 CKEDITOR.env.ie && ( depressBeforeEvent = 1 );\r
383 \r
384                                                 var retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;\r
385                                                 depressBeforeEvent = 0;\r
386                                                 return retval;\r
387                                         }\r
388 \r
389                                         editor.contextMenu.addListener( function( element, selection )\r
390                                                 {\r
391                                                         var readOnly = selection.getCommonAncestor().isReadOnly();\r
392                                                         return {\r
393                                                                 cut : !readOnly && stateFromNamedCommand( 'Cut' ),\r
394                                                                 copy : stateFromNamedCommand( 'Copy' ),\r
395                                                                 paste : !readOnly && ( CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' ) )\r
396                                                         };\r
397                                                 });\r
398                                 }\r
399                         }\r
400                 });\r
401 })();\r
402 \r
403 /**\r
404  * Fired when a clipboard operation is about to be taken into the editor.\r
405  * Listeners can manipulate the data to be pasted before having it effectively\r
406  * inserted into the document.\r
407  * @name CKEDITOR.editor#paste\r
408  * @since 3.1\r
409  * @event\r
410  * @param {String} [data.html] The HTML data to be pasted. If not available, e.data.text will be defined.\r
411  * @param {String} [data.text] The plain text data to be pasted, available when plain text operations are to used. If not available, e.data.html will be defined.\r
412  */\r