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