1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 /**
  7  * @fileOverview The "toolbar" plugin. Renders the default toolbar interface in
  8  * the editor.
  9  */
 10 
 11 (function()
 12 {
 13 	var toolbox = function()
 14 	{
 15 		this.toolbars = [];
 16 		this.focusCommandExecuted = false;
 17 	};
 18 
 19 	toolbox.prototype.focus = function()
 20 	{
 21 		for ( var t = 0, toolbar ; toolbar = this.toolbars[ t++ ] ; )
 22 		{
 23 			for ( var i = 0, item ; item = toolbar.items[ i++ ] ; )
 24 			{
 25 				if ( item.focus )
 26 				{
 27 					item.focus();
 28 					return;
 29 				}
 30 			}
 31 		}
 32 	};
 33 
 34 	var commands =
 35 	{
 36 		toolbarFocus :
 37 		{
 38 			modes : { wysiwyg : 1, source : 1 },
 39 			readOnly : 1,
 40 
 41 			exec : function( editor )
 42 			{
 43 				if ( editor.toolbox )
 44 				{
 45 					editor.toolbox.focusCommandExecuted = true;
 46 
 47 					// Make the first button focus accessible for IE. (#3417)
 48 					// Adobe AIR instead need while of delay.
 49 					if ( CKEDITOR.env.ie || CKEDITOR.env.air )
 50 						setTimeout( function(){ editor.toolbox.focus(); }, 100 );
 51 					else
 52 						editor.toolbox.focus();
 53 				}
 54 			}
 55 		}
 56 	};
 57 
 58 	CKEDITOR.plugins.add( 'toolbar',
 59 	{
 60 		requires : [ 'button' ],
 61 		init : function( editor )
 62 		{
 63 			var endFlag;
 64 
 65 			var itemKeystroke = function( item, keystroke )
 66 			{
 67 				var next, toolbar;
 68 				var rtl = editor.lang.dir == 'rtl',
 69 					toolbarGroupCycling = editor.config.toolbarGroupCycling;
 70 
 71 				toolbarGroupCycling = toolbarGroupCycling === undefined || toolbarGroupCycling;
 72 
 73 				switch ( keystroke )
 74 				{
 75 					case 9 :					// TAB
 76 					case CKEDITOR.SHIFT + 9 :	// SHIFT + TAB
 77 						// Cycle through the toolbars, starting from the one
 78 						// closest to the current item.
 79 						while ( !toolbar || !toolbar.items.length )
 80 						{
 81 							toolbar = keystroke == 9 ?
 82 								( ( toolbar ? toolbar.next : item.toolbar.next ) || editor.toolbox.toolbars[ 0 ] ) :
 83 								( ( toolbar ? toolbar.previous : item.toolbar.previous ) || editor.toolbox.toolbars[ editor.toolbox.toolbars.length - 1 ] );
 84 
 85 							// Look for the first item that accepts focus.
 86 							if ( toolbar.items.length )
 87 							{
 88 								item = toolbar.items[ endFlag ? ( toolbar.items.length - 1 ) : 0 ];
 89 								while ( item && !item.focus )
 90 								{
 91 									item = endFlag ? item.previous : item.next;
 92 
 93 									if ( !item )
 94 										toolbar = 0;
 95 								}
 96 							}
 97 						}
 98 
 99 						if ( item )
100 							item.focus();
101 
102 						return false;
103 
104 					case rtl ? 37 : 39 :		// RIGHT-ARROW
105 					case 40 :					// DOWN-ARROW
106 						next = item;
107 						do
108 						{
109 							// Look for the next item in the toolbar.
110 							next = next.next;
111 
112 							// If it's the last item, cycle to the first one.
113 							if ( !next && toolbarGroupCycling )
114 								next = item.toolbar.items[ 0 ];
115 						}
116 						while ( next && !next.focus )
117 
118 						// If available, just focus it, otherwise focus the
119 						// first one.
120 						if ( next )
121 							next.focus();
122 						else
123 							// Send a TAB.
124 							itemKeystroke( item, 9 );
125 
126 						return false;
127 
128 					case rtl ? 39 : 37 :		// LEFT-ARROW
129 					case 38 :					// UP-ARROW
130 						next = item;
131 						do
132 						{
133 							// Look for the previous item in the toolbar.
134 							next = next.previous;
135 
136 							// If it's the first item, cycle to the last one.
137 							if ( !next && toolbarGroupCycling )
138 								next = item.toolbar.items[ item.toolbar.items.length - 1 ];
139 						}
140 						while ( next && !next.focus )
141 
142 						// If available, just focus it, otherwise focus the
143 						// last one.
144 						if ( next )
145 							next.focus();
146 						else
147 						{
148 							endFlag = 1;
149 							// Send a SHIFT + TAB.
150 							itemKeystroke( item, CKEDITOR.SHIFT + 9 );
151 							endFlag = 0;
152 						}
153 
154 						return false;
155 
156 					case 27 :					// ESC
157 						editor.focus();
158 						return false;
159 
160 					case 13 :					// ENTER
161 					case 32 :					// SPACE
162 						item.execute();
163 						return false;
164 				}
165 				return true;
166 			};
167 
168 			editor.on( 'themeSpace', function( event )
169 				{
170 					if ( event.data.space == editor.config.toolbarLocation )
171 					{
172 						editor.toolbox = new toolbox();
173 
174 						var labelId = CKEDITOR.tools.getNextId();
175 
176 						var output = [ '<div class="cke_toolbox" role="group" aria-labelledby="', labelId, '" onmousedown="return false;"' ],
177 							expanded =  editor.config.toolbarStartupExpanded !== false,
178 							groupStarted;
179 
180 						output.push( expanded ? '>' : ' style="display:none">' );
181 
182 						// Sends the ARIA label.
183 						output.push( '<span id="', labelId, '" class="cke_voice_label">', editor.lang.toolbars, '</span>' );
184 
185 						var toolbars = editor.toolbox.toolbars,
186 							toolbar =
187 									( editor.config.toolbar instanceof Array ) ?
188 										editor.config.toolbar
189 									:
190 										editor.config[ 'toolbar_' + editor.config.toolbar ];
191 
192 						for ( var r = 0 ; r < toolbar.length ; r++ )
193 						{
194 							var toolbarId,
195 								toolbarObj = 0,
196 								toolbarName,
197 								row = toolbar[ r ],
198 								items;
199 
200 							// It's better to check if the row object is really
201 							// available because it's a common mistake to leave
202 							// an extra comma in the toolbar definition
203 							// settings, which leads on the editor not loading
204 							// at all in IE. (#3983)
205 							if ( !row )
206 								continue;
207 
208 							if ( groupStarted )
209 							{
210 								output.push( '</div>' );
211 								groupStarted = 0;
212 							}
213 
214 							if ( row === '/' )
215 							{
216 								output.push( '<div class="cke_break"></div>' );
217 								continue;
218 							}
219 
220 							items = row.items || row;
221 
222 							// Create all items defined for this toolbar.
223 							for ( var i = 0 ; i < items.length ; i++ )
224 							{
225 								var item,
226 									itemName = items[ i ],
227 									canGroup;
228 
229 								item = editor.ui.create( itemName );
230 
231 								if ( item )
232 								{
233 									canGroup = item.canGroup !== false;
234 
235 									// Initialize the toolbar first, if needed.
236 									if ( !toolbarObj )
237 									{
238 										// Create the basic toolbar object.
239 										toolbarId = CKEDITOR.tools.getNextId();
240 										toolbarObj = { id : toolbarId, items : [] };
241 										toolbarName = row.name && ( editor.lang.toolbarGroups[ row.name ] || row.name );
242 
243 										// Output the toolbar opener.
244 										output.push( '<span id="', toolbarId, '" class="cke_toolbar"',
245 											( toolbarName ? ' aria-labelledby="'+ toolbarId +  '_label"' : '' ),
246 											' role="toolbar">' );
247 
248 										// If a toolbar name is available, send the voice label.
249 										toolbarName && output.push( '<span id="', toolbarId, '_label" class="cke_voice_label">', toolbarName, '</span>' );
250 
251 										output.push( '<span class="cke_toolbar_start"></span>' );
252 
253 										// Add the toolbar to the "editor.toolbox.toolbars"
254 										// array.
255 										var index = toolbars.push( toolbarObj ) - 1;
256 
257 										// Create the next/previous reference.
258 										if ( index > 0 )
259 										{
260 											toolbarObj.previous = toolbars[ index - 1 ];
261 											toolbarObj.previous.next = toolbarObj;
262 										}
263 									}
264 
265 									if ( canGroup )
266 									{
267 										if ( !groupStarted )
268 										{
269 											output.push( '<span class="cke_toolgroup" role="presentation">' );
270 											groupStarted = 1;
271 										}
272 									}
273 									else if ( groupStarted )
274 									{
275 										output.push( '</span>' );
276 										groupStarted = 0;
277 									}
278 
279 									var itemObj = item.render( editor, output );
280 									index = toolbarObj.items.push( itemObj ) - 1;
281 
282 									if ( index > 0 )
283 									{
284 										itemObj.previous = toolbarObj.items[ index - 1 ];
285 										itemObj.previous.next = itemObj;
286 									}
287 
288 									itemObj.toolbar = toolbarObj;
289 									itemObj.onkey = itemKeystroke;
290 
291 									/*
292 									 * Fix for #3052:
293 									 * Prevent JAWS from focusing the toolbar after document load.
294 									 */
295 									itemObj.onfocus = function()
296 									{
297 										if ( !editor.toolbox.focusCommandExecuted )
298 											editor.focus();
299 									};
300 								}
301 							}
302 
303 							if ( groupStarted )
304 							{
305 								output.push( '</span>' );
306 								groupStarted = 0;
307 							}
308 
309 							if ( toolbarObj )
310 								output.push( '<span class="cke_toolbar_end"></span></span>' );
311 						}
312 
313 						output.push( '</div>' );
314 
315 						if ( editor.config.toolbarCanCollapse )
316 						{
317 							var collapserFn = CKEDITOR.tools.addFunction(
318 								function()
319 								{
320 									editor.execCommand( 'toolbarCollapse' );
321 								});
322 
323 							editor.on( 'destroy', function () {
324 									CKEDITOR.tools.removeFunction( collapserFn );
325 								});
326 
327 							var collapserId = CKEDITOR.tools.getNextId();
328 
329 							editor.addCommand( 'toolbarCollapse',
330 								{
331 									readOnly : 1,
332 									exec : function( editor )
333 									{
334 										var collapser = CKEDITOR.document.getById( collapserId ),
335 											toolbox = collapser.getPrevious(),
336 											contents = editor.getThemeSpace( 'contents' ),
337 											toolboxContainer = toolbox.getParent(),
338 											contentHeight = parseInt( contents.$.style.height, 10 ),
339 											previousHeight = toolboxContainer.$.offsetHeight,
340 											collapsed = !toolbox.isVisible();
341 
342 										if ( !collapsed )
343 										{
344 											toolbox.hide();
345 											collapser.addClass( 'cke_toolbox_collapser_min' );
346 											collapser.setAttribute( 'title', editor.lang.toolbarExpand );
347 										}
348 										else
349 										{
350 											toolbox.show();
351 											collapser.removeClass( 'cke_toolbox_collapser_min' );
352 											collapser.setAttribute( 'title', editor.lang.toolbarCollapse );
353 										}
354 
355 										// Update collapser symbol.
356 										collapser.getFirst().setText( collapsed ?
357 											'\u25B2' :		// BLACK UP-POINTING TRIANGLE
358 											'\u25C0' );		// BLACK LEFT-POINTING TRIANGLE
359 
360 										var dy = toolboxContainer.$.offsetHeight - previousHeight;
361 										contents.setStyle( 'height', ( contentHeight - dy ) + 'px' );
362 
363 										editor.fire( 'resize' );
364 									},
365 
366 									modes : { wysiwyg : 1, source : 1 }
367 								} );
368 
369 							output.push( '<a title="' + ( expanded ? editor.lang.toolbarCollapse : editor.lang.toolbarExpand )
370 													  + '" id="' + collapserId + '" tabIndex="-1" class="cke_toolbox_collapser' );
371 
372 							if ( !expanded )
373 								output.push( ' cke_toolbox_collapser_min' );
374 
375 							output.push( '" onclick="CKEDITOR.tools.callFunction(' + collapserFn + ')">',
376 										'<span>▲</span>',		// BLACK UP-POINTING TRIANGLE
377 										'</a>' );
378 						}
379 
380 						event.data.html += output.join( '' );
381 					}
382 				});
383 
384 			editor.on( 'destroy', function()
385 			{
386 				var toolbars, index = 0, i,
387 						items, instance;
388 				toolbars = this.toolbox.toolbars;
389 				for ( ; index < toolbars.length; index++ )
390 				{
391 					items = toolbars[ index ].items;
392 					for ( i = 0; i < items.length; i++ )
393 					{
394 						instance = items[ i ];
395 						if ( instance.clickFn ) CKEDITOR.tools.removeFunction( instance.clickFn );
396 						if ( instance.keyDownFn ) CKEDITOR.tools.removeFunction( instance.keyDownFn );
397 					}
398 				}
399 			});
400 
401 			editor.addCommand( 'toolbarFocus', commands.toolbarFocus );
402 
403 			editor.ui.add( '-', CKEDITOR.UI_SEPARATOR, {} );
404 			editor.ui.addHandler( CKEDITOR.UI_SEPARATOR,
405 			{
406 				create: function()
407 				{
408 					return {
409 						render : function( editor, output )
410 						{
411 							output.push( '<span class="cke_separator" role="separator"></span>' );
412 							return {};
413 						}
414 					};
415 				}
416 			});
417 		}
418 	});
419 })();
420 
421 CKEDITOR.UI_SEPARATOR = 'separator';
422 
423 /**
424  * The "theme space" to which rendering the toolbar. For the default theme,
425  * the recommended options are "top" and "bottom".
426  * @type String
427  * @default 'top'
428  * @see CKEDITOR.config.theme
429  * @example
430  * config.toolbarLocation = 'bottom';
431  */
432 CKEDITOR.config.toolbarLocation = 'top';
433 
434 /**
435  * The toolbar definition. It is an array of toolbars (strips),
436  * each one being also an array, containing a list of UI items.
437  * Note that this setting is composed by "toolbar_" added by the toolbar name,
438  * which in this case is called "Basic". This second part of the setting name
439  * can be anything. You must use this name in the
440  * {@link CKEDITOR.config.toolbar} setting, so you instruct the editor which
441  * toolbar_(name) setting to you.
442  * @type Array
443  * @example
444  * // Defines a toolbar with only one strip containing the "Source" button, a
445  * // separator and the "Bold" and "Italic" buttons.
446  * <b>config.toolbar_Basic =
447  * [
448  *     [ 'Source', '-', 'Bold', 'Italic' ]
449  * ]</b>;
450  * config.toolbar = 'Basic';
451  */
452 CKEDITOR.config.toolbar_Basic =
453 [
454 	['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink','-','About']
455 ];
456 
457 /**
458  * This is the default toolbar definition used by the editor. It contains all
459  * editor features.
460  * @type Array
461  * @default (see example)
462  * @example
463  * // This is actually the default value.
464  * config.toolbar_Full =
465  * [
466  *     { name: 'document',    items : [ 'Source','-','Save','NewPage','DocProps','Preview','Print','-','Templates' ] },
467  *     { name: 'clipboard',   items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
468  *     { name: 'editing',     items : [ 'Find','Replace','-','SelectAll','-','SpellChecker', 'Scayt' ] },
469  *     { name: 'forms',       items : [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
470  *     '/',
471  *     { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
472  *     { name: 'paragraph',   items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl' ] },
473  *     { name: 'links',       items : [ 'Link','Unlink','Anchor' ] },
474  *     { name: 'insert',      items : [ 'Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak' ] },
475  *     '/',
476  *     { name: 'styles',      items : [ 'Styles','Format','Font','FontSize' ] },
477  *     { name: 'colors',      items : [ 'TextColor','BGColor' ] },
478  *     { name: 'tools',       items : [ 'Maximize', 'ShowBlocks','-','About' ] }
479  * ];
480  */
481 CKEDITOR.config.toolbar_Full =
482 [
483 	{ name: 'document',		items : [ 'Source','-','Save','NewPage','DocProps','Preview','Print','-','Templates' ] },
484 	{ name: 'clipboard',	items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
485 	{ name: 'editing',		items : [ 'Find','Replace','-','SelectAll','-','SpellChecker', 'Scayt' ] },
486 	{ name: 'forms',		items : [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
487 	'/',
488 	{ name: 'basicstyles',	items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
489 	{ name: 'paragraph',	items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl' ] },
490 	{ name: 'links',		items : [ 'Link','Unlink','Anchor' ] },
491 	{ name: 'insert',		items : [ 'Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak','Iframe' ] },
492 	'/',
493 	{ name: 'styles',		items : [ 'Styles','Format','Font','FontSize' ] },
494 	{ name: 'colors',		items : [ 'TextColor','BGColor' ] },
495 	{ name: 'tools',		items : [ 'Maximize', 'ShowBlocks','-','About' ] }
496 ];
497 
498 /**
499  * The toolbox (alias toolbar) definition. It is a toolbar name or an array of
500  * toolbars (strips), each one being also an array, containing a list of UI items.
501  * @type Array|String
502  * @default 'Full'
503  * @example
504  * // Defines a toolbar with only one strip containing the "Source" button, a
505  * // separator and the "Bold" and "Italic" buttons.
506  * config.toolbar =
507  * [
508  *     [ 'Source', '-', 'Bold', 'Italic' ]
509  * ];
510  * @example
511  * // Load toolbar_Name where Name = Basic.
512  * config.toolbar = 'Basic';
513  */
514 CKEDITOR.config.toolbar = 'Full';
515 
516 /**
517  * Whether the toolbar can be collapsed by the user. If disabled, the collapser
518  * button will not be displayed.
519  * @type Boolean
520  * @default true
521  * @example
522  * config.toolbarCanCollapse = false;
523  */
524 CKEDITOR.config.toolbarCanCollapse = true;
525 
526 /**
527  * Whether the toolbar must start expanded when the editor is loaded.
528  * @name CKEDITOR.config.toolbarStartupExpanded
529  * @type Boolean
530  * @default true
531  * @example
532  * config.toolbarStartupExpanded = false;
533  */
534 
535 /**
536  * When enabled, makes the arrow keys navigation cycle within the current
537  * toolbar group. Otherwise the arrows will move trought all items available in
538  * the toolbar. The TAB key will still be used to quickly jump among the
539  * toolbar groups.
540  * @name CKEDITOR.config.toolbarGroupCycling
541  * @since 3.6
542  * @type Boolean
543  * @default true
544  * @example
545  * config.toolbarGroupCycling = false;
546  */
547