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