5b6878acbc8a4d92699c8fb2e37e4e31beaff3ac
[ckeditor.git] / skins / ckeditor / _source / plugins / toolbar / 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 * @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>&#9650;</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 */