JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.1
[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                 doc.$.execCommand( command );\r
32 \r
33                 body.removeListener( command, onExec );\r
34 \r
35                 return enabled;\r
36         };\r
37 \r
38         // Attempts to execute the Cut and Copy operations.\r
39         var tryToCutCopy =\r
40                 CKEDITOR.env.ie ?\r
41                         function( editor, type )\r
42                         {\r
43                                 return execIECommand( editor, type );\r
44                         }\r
45                 :               // !IE.\r
46                         function( editor, type )\r
47                         {\r
48                                 try\r
49                                 {\r
50                                         // Other browsers throw an error if the command is disabled.\r
51                                         return editor.document.$.execCommand( type );\r
52                                 }\r
53                                 catch( e )\r
54                                 {\r
55                                         return false;\r
56                                 }\r
57                         };\r
58 \r
59         // A class that represents one of the cut or copy commands.\r
60         var cutCopyCmd = function( type )\r
61         {\r
62                 this.type = type;\r
63                 this.canUndo = ( this.type == 'cut' );          // We can't undo copy to clipboard.\r
64         };\r
65 \r
66         cutCopyCmd.prototype =\r
67         {\r
68                 exec : function( editor, data )\r
69                 {\r
70                         var success = tryToCutCopy( editor, this.type );\r
71 \r
72                         if ( !success )\r
73                                 alert( editor.lang.clipboard[ this.type + 'Error' ] );          // Show cutError or copyError.\r
74 \r
75                         return success;\r
76                 }\r
77         };\r
78 \r
79         // Paste command.\r
80         var pasteCmd =\r
81         {\r
82                 canUndo : false,\r
83 \r
84                 exec :\r
85                         CKEDITOR.env.ie ?\r
86                                 function( editor )\r
87                                 {\r
88                                         // Prevent IE from pasting at the begining of the document.\r
89                                         editor.focus();\r
90 \r
91                                         if ( !editor.document.getBody().fire( 'beforepaste' )\r
92                                                  && !execIECommand( editor, 'paste' ) )\r
93                                         {\r
94                                                 editor.fire( 'pasteDialog' );\r
95                                                 return false;\r
96                                         }\r
97                                 }\r
98                         :\r
99                                 function( editor )\r
100                                 {\r
101                                         try\r
102                                         {\r
103                                                 if ( !editor.document.getBody().fire( 'beforepaste' )\r
104                                                          && !editor.document.$.execCommand( 'Paste', false, null ) )\r
105                                                 {\r
106                                                         throw 0;\r
107                                                 }\r
108                                         }\r
109                                         catch ( e )\r
110                                         {\r
111                                                 setTimeout( function()\r
112                                                         {\r
113                                                                 editor.fire( 'pasteDialog' );\r
114                                                         }, 0 );\r
115                                                 return false;\r
116                                         }\r
117                                 }\r
118         };\r
119 \r
120         // Listens for some clipboard related keystrokes, so they get customized.\r
121         var onKey = function( event )\r
122         {\r
123                 if ( this.mode != 'wysiwyg' )\r
124                         return;\r
125 \r
126                 switch ( event.data.keyCode )\r
127                 {\r
128                         // Paste\r
129                         case CKEDITOR.CTRL + 86 :               // CTRL+V\r
130                         case CKEDITOR.SHIFT + 45 :              // SHIFT+INS\r
131 \r
132                                 var body = this.document.getBody();\r
133 \r
134                                 // Simulate 'beforepaste' event for all none-IEs.\r
135                                 if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) )\r
136                                         event.cancel();\r
137                                 // Simulate 'paste' event for Opera/Firefox2.\r
138                                 else if ( CKEDITOR.env.opera\r
139                                                  || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )\r
140                                         body.fire( 'paste' );\r
141                                 return;\r
142 \r
143                         // Cut\r
144                         case CKEDITOR.CTRL + 88 :               // CTRL+X\r
145                         case CKEDITOR.SHIFT + 46 :              // SHIFT+DEL\r
146 \r
147                                 // Save Undo snapshot.\r
148                                 var editor = this;\r
149                                 this.fire( 'saveSnapshot' );            // Save before paste\r
150                                 setTimeout( function()\r
151                                         {\r
152                                                 editor.fire( 'saveSnapshot' );          // Save after paste\r
153                                         }, 0 );\r
154                 }\r
155         };\r
156 \r
157         // Allow to peek clipboard content by redirecting the\r
158         // pasting content into a temporary bin and grab the content of it.\r
159         function getClipboardData( evt, mode, callback )\r
160         {\r
161                 var doc = this.document;\r
162 \r
163                 // Avoid recursions on 'paste' event for IE.\r
164                 if ( CKEDITOR.env.ie && doc.getById( 'cke_pastebin' ) )\r
165                         return;\r
166 \r
167                 var sel = this.getSelection(),\r
168                         range = new CKEDITOR.dom.range( doc );\r
169 \r
170                 // Create container to paste into\r
171                 var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : 'div', doc );\r
172                 pastebin.setAttribute( 'id', 'cke_pastebin' );\r
173                 // Safari requires a filler node inside the div to have the content pasted into it. (#4882)\r
174                 CKEDITOR.env.webkit && pastebin.append( doc.createText( '\xa0' ) );\r
175                 doc.getBody().append( pastebin );\r
176 \r
177                 // It's definitely a better user experience if we make the paste-bin pretty unnoticed\r
178                 // by pulling it off the screen, while this hack will make the paste-bin a control type element\r
179                 // and that become a selection plain later.\r
180                 if ( !CKEDITOR.env.ie && mode != 'html' )\r
181                 {\r
182                         pastebin.setStyles(\r
183                                 {\r
184                                         position : 'absolute',\r
185                                         left : '-1000px',\r
186                                         // Position the bin exactly at the position of the selected element\r
187                                         // to avoid any subsequent document scroll.\r
188                                         top : sel.getStartElement().getDocumentPosition().y + 'px',\r
189                                         width : '1px',\r
190                                         height : '1px',\r
191                                         overflow : 'hidden'\r
192                                 });\r
193                 }\r
194 \r
195                 var bms = sel.createBookmarks();\r
196 \r
197                 // Turn off design mode temporarily before give focus to the paste bin.\r
198                 if ( mode == 'text' )\r
199                 {\r
200                         if ( CKEDITOR.env.ie )\r
201                         {\r
202                                 var ieRange = doc.getBody().$.createTextRange();\r
203                                 ieRange.moveToElementText( pastebin.$ );\r
204                                 ieRange.execCommand( 'Paste' );\r
205                                 evt.data.preventDefault();\r
206                         }\r
207                         else\r
208                         {\r
209                                 doc.$.designMode = 'off';\r
210                                 pastebin.$.focus();\r
211                         }\r
212                 }\r
213                 else\r
214                 {\r
215                         range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START );\r
216                         range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END );\r
217                         range.select( true );\r
218                 }\r
219 \r
220                 // Wait a while and grab the pasted contents\r
221                 window.setTimeout( function()\r
222                 {\r
223                         mode == 'text' && !CKEDITOR.env.ie && ( doc.$.designMode = 'on' );\r
224                         pastebin.remove();\r
225 \r
226                         // Grab the HTML contents.\r
227                         // We need to look for a apple style wrapper on webkit it also adds\r
228                         // a div wrapper if you copy/paste the body of the editor.\r
229                         // Remove hidden div and restore selection.\r
230                         var bogusSpan;\r
231                         pastebin = ( CKEDITOR.env.webkit\r
232                                                  && ( bogusSpan = pastebin.getFirst() )\r
233                                                  && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ?\r
234                                                         bogusSpan : pastebin );\r
235 \r
236                         sel.selectBookmarks( bms );\r
237                         callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() );\r
238                 }, 0 );\r
239         }\r
240 \r
241         // Register the plugin.\r
242         CKEDITOR.plugins.add( 'clipboard',\r
243                 {\r
244                         requires : [ 'htmldataprocessor' ],\r
245                         init : function( editor )\r
246                         {\r
247                                 // Inserts processed data into the editor at the end of the\r
248                                 // events chain.\r
249                                 editor.on( 'paste', function( evt )\r
250                                         {\r
251                                                 var data = evt.data;\r
252                                                 if ( data[ 'html' ] )\r
253                                                         editor.insertHtml( data[ 'html' ] );\r
254                                                 else if ( data[ 'text' ] )\r
255                                                         editor.insertText( data[ 'text' ] );\r
256 \r
257                                         }, null, null, 1000 );\r
258 \r
259                                 editor.on( 'pasteDialog', function( evt )\r
260                                         {\r
261                                                 setTimeout( function()\r
262                                                 {\r
263                                                         // Open default paste dialog.\r
264                                                         editor.openDialog( 'paste' );\r
265                                                 }, 0 );\r
266                                         });\r
267 \r
268                                 function addButtonCommand( buttonName, commandName, command, ctxMenuOrder )\r
269                                 {\r
270                                         var lang = editor.lang[ commandName ];\r
271 \r
272                                         editor.addCommand( commandName, command );\r
273                                         editor.ui.addButton( buttonName,\r
274                                                 {\r
275                                                         label : lang,\r
276                                                         command : commandName\r
277                                                 });\r
278 \r
279                                         // If the "menu" plugin is loaded, register the menu item.\r
280                                         if ( editor.addMenuItems )\r
281                                         {\r
282                                                 editor.addMenuItem( commandName,\r
283                                                         {\r
284                                                                 label : lang,\r
285                                                                 command : commandName,\r
286                                                                 group : 'clipboard',\r
287                                                                 order : ctxMenuOrder\r
288                                                         });\r
289                                         }\r
290                                 }\r
291 \r
292                                 addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 );\r
293                                 addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 );\r
294                                 addButtonCommand( 'Paste', 'paste', pasteCmd, 8 );\r
295 \r
296                                 CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );\r
297 \r
298                                 editor.on( 'key', onKey, editor );\r
299 \r
300                                 var mode = editor.config.forcePasteAsPlainText ? 'text' : 'html';\r
301 \r
302                                 // We'll be catching all pasted content in one line, regardless of whether the\r
303                                 // it's introduced by a document command execution (e.g. toolbar buttons) or\r
304                                 // user paste behaviors. (e.g. Ctrl-V)\r
305                                 editor.on( 'contentDom', function()\r
306                                 {\r
307                                         var body = editor.document.getBody();\r
308                                         body.on( ( mode == 'text' && CKEDITOR.env.ie ) ? 'paste' : 'beforepaste',\r
309                                                 function( evt )\r
310                                                 {\r
311                                                         if( depressBeforePasteEvent )\r
312                                                                 return;\r
313 \r
314                                                         getClipboardData.call( editor, evt, mode, function ( data )\r
315                                                         {\r
316                                                                 // The very last guard to make sure the\r
317                                                                 // paste has successfully happened.\r
318                                                                 if ( !data )\r
319                                                                         return;\r
320 \r
321                                                                 var dataTransfer = {};\r
322                                                                 dataTransfer[ mode ] = data;\r
323                                                                 editor.fire( 'paste', dataTransfer );\r
324                                                         } );\r
325                                                 });\r
326 \r
327                                 });\r
328 \r
329                                 // If the "contextmenu" plugin is loaded, register the listeners.\r
330                                 if ( editor.contextMenu )\r
331                                 {\r
332                                         var depressBeforePasteEvent;\r
333                                         function stateFromNamedCommand( command )\r
334                                         {\r
335                                                 // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste',\r
336                                                 // guard to distinguish from the ordinary sources( either\r
337                                                 // keyboard paste or execCommand ) (#4874).\r
338                                                 CKEDITOR.env.ie && command == 'Paste'&& ( depressBeforePasteEvent = 1 );\r
339 \r
340                                                 var retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;\r
341                                                 depressBeforePasteEvent = 0;\r
342                                                 return retval;\r
343                                         }\r
344 \r
345                                         editor.contextMenu.addListener( function()\r
346                                                 {\r
347                                                         return {\r
348                                                                 cut : stateFromNamedCommand( 'Cut' ),\r
349 \r
350                                                                 // Browser bug: 'Cut' has the correct states for both Copy and Cut.\r
351                                                                 copy : stateFromNamedCommand( 'Cut' ),\r
352                                                                 paste : CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' )\r
353                                                         };\r
354                                                 });\r
355                                 }\r
356                         }\r
357                 });\r
358 })();\r