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