Ticket #3839: 3839_2.patch
File 3839_2.patch, 21.9 KB (added by , 15 years ago) |
---|
-
_source/plugins/scayt/dialogs/options.js
8 8 var firstLoad = true, 9 9 captions, 10 10 doc = CKEDITOR.document, 11 fckLang = 'en'; 11 tags = [], 12 i, 13 contents = [], 14 userDicActive = false; 12 15 var dic_buttons = [ 13 16 // [0] contains buttons for creating 14 17 "dic_create,dic_restore", 15 18 // [1] contains buton for manipulation 16 19 "dic_rename,dic_delete" 17 20 ]; 21 var tags_contents = [ 22 { 23 id : 'options', 24 label : editor.lang.scayt.optionsTab, 25 elements : [ 26 { 27 type : 'html', 28 id : 'options', 29 html : '<div class="inner_options">' + 30 ' <div class="messagebox"></div>' + 31 ' <div style="display:none;">' + 32 ' <input type="checkbox" value="0" id="allCaps" />' + 33 ' <label for="allCaps" id="label_allCaps"></label>' + 34 ' </div>' + 35 ' <div style="display:none;">' + 36 ' <input type="checkbox" value="0" id="ignoreDomainNames" />' + 37 ' <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>' + 38 ' </div>' + 39 ' <div style="display:none;">' + 40 ' <input type="checkbox" value="0" id="mixedCase" />' + 41 ' <label for="mixedCase" id="label_mixedCase"></label>' + 42 ' </div>' + 43 ' <div style="display:none;">' + 44 ' <input type="checkbox" value="0" id="mixedWithDigits" />' + 45 ' <label for="mixedWithDigits" id="label_mixedWithDigits"></label>' + 46 ' </div>' + 47 '</div>' 48 } 49 ] 50 }, 51 { 52 id : 'langs', 53 label : editor.lang.scayt.languagesTab, 54 elements : [ 55 { 56 type : 'html', 57 id : 'langs', 58 html : '<div class="inner_langs">' + 59 ' <div class="messagebox"></div> ' + 60 ' <div style="float:left;width:47%;margin-left:5px;" id="scayt_lcol" ></div>' + 61 ' <div style="float:left;width:47%;margin-left:15px;" id="scayt_rcol"></div>' + 62 '</div>' 63 } 64 ] 65 }, 66 { 67 id : 'dictionaries', 68 label : editor.lang.scayt.dictionariesTab, 69 elements : [ 70 { 71 type : 'html', 72 style: '', 73 id : 'dic', 74 html : '<div class="inner_dictionary" style="text-align:left; white-space:normal;">' + 75 ' <div style="margin:5px auto; width:80%;white-space:normal; overflow:hidden;" id="dic_message"> </div>' + 76 ' <div style="margin:5px auto; width:80%;white-space:normal;"> ' + 77 ' <span class="cke_dialog_ui_labeled_label" >Dictionary name</span><br>'+ 78 ' <span class="cke_dialog_ui_labeled_content" >'+ 79 ' <div class="cke_dialog_ui_input_text">'+ 80 ' <input id="dic_name" type="text" class="cke_dialog_ui_input_text"/>'+ 81 ' </div></span></div>'+ 82 ' <div style="margin:5px auto; width:80%;white-space:normal;">'+ 83 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_create">'+ 84 ' </a>' + 85 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_delete">'+ 86 ' </a>' + 87 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_rename">'+ 88 ' </a>' + 89 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_restore">'+ 90 ' </a>' + 91 ' </div>' + 92 ' <div style="margin:5px auto; width:95%;white-space:normal;" id="dic_info"></div>' + 93 '</div>' 94 } 95 ] 96 }, 97 { 98 id : 'about', 99 label : editor.lang.scayt.aboutTab, 100 elements : [ 101 { 102 type : 'html', 103 id : 'about', 104 style : 'margin: 10px 40px;', 105 html : '<div id="scayt_about"></div>' 106 } 107 ] 108 } 109 ]; 110 var dialogDefiniton = { 111 title : editor.lang.scayt.title, 112 minWidth : 340, 113 minHeight : 200, 114 onShow : function() 115 { 116 var dialog = this; 117 dialog.data = editor.fire( 'scaytDialog', {} ); 118 dialog.options = dialog.data.scayt_control.option(); 119 dialog.sLang = dialog.data.scayt_control.sLang; 18 120 121 if ( !dialog.data || !dialog.data.scayt || !dialog.data.scayt_control ) 122 { 123 alert( 'Error loading application service' ); 124 dialog.hide(); 125 return; 126 } 127 128 var stop = 0; 129 if ( firstLoad ) 130 { 131 dialog.data.scayt.getCaption( 'en', function( caps ) 132 { 133 if ( stop++ > 0 ) // Once only 134 return; 135 captions = caps; 136 init_with_captions.apply( dialog ); 137 reload.apply( dialog ); 138 firstLoad = false; 139 }); 140 } 141 else 142 reload.apply( dialog ); 143 144 dialog.selectPage( dialog.data.tab ); 145 }, 146 onOk : function() 147 { 148 var scayt_control = this.data.scayt_control, 149 o = scayt_control.option(), 150 c = 0; 151 152 // Set up options if any was set. 153 for ( var i in this.options ) 154 { 155 if (o[i] != this.options[ i ] && c === 0 ) 156 { 157 scayt_control.option( this.options ); 158 c++; 159 } 160 } 161 162 // Setup languge if it was changed. 163 var csLang = this.chosed_lang; 164 if ( csLang && this.data.sLang != csLang ) 165 { 166 scayt_control.setLang( csLang ); 167 c++; 168 } 169 if ( c > 0 ) 170 scayt_control.refresh(); 171 }, 172 contents : contents 173 }; 174 175 var scayt_control = CKEDITOR.plugins.scayt.getScayt( editor ); 176 if ( scayt_control ) 177 { 178 tags = scayt_control.uiTags; 179 } 180 181 for ( i in tags ) { 182 if ( tags[ i ] == 1 ) 183 contents[ contents.length ] = tags_contents[ i ]; 184 } 185 if ( tags[2] == 1 ) 186 userDicActive = true; 187 188 function onDicButtonClick() 189 { 190 var dic_name = doc.getById('dic_name').getValue(); 191 if ( !dic_name ) 192 { 193 dic_error_message(" Dictionary name should not be empty. "); 194 return false; 195 } 196 //apply handler 197 dic[ this.getId() ].apply( null, [ this, dic_name, dic_buttons ] ); 198 199 return true; 200 } 19 201 var init_with_captions = function() 20 202 { 21 203 var dialog = this, … … 25 207 i; 26 208 27 209 // Add buttons titles 28 for ( i in buttons ) 29 { 30 var button = buttons[ i ]; 31 doc.getById( button ).setHtml( '<span class="cke_dialog_ui_button">' + captions[ 'button_' + button] +'</span>' ); 32 } 33 doc.getById( 'dic_info' ).setHtml( captions[ 'dic_info' ] ); 34 210 if (userDicActive) 211 { 212 for ( i in buttons ) 213 { 214 var button = buttons[ i ]; 215 doc.getById( button ).setHtml( '<span class="cke_dialog_ui_button">' + captions[ 'button_' + button] +'</span>' ); 216 } 217 doc.getById( 'dic_info' ).setHtml( captions[ 'dic_info' ] ); 218 } 219 220 35 221 // Fill options and dictionary labels. 36 222 for ( i in labels ) 37 223 { … … 217 403 for ( i = 0, l = arr_buttons.length ; i < l ; i += 1 ) 218 404 { 219 405 var dic_button = doc.getById(arr_buttons[i]); 220 221 dic_button.on( 'click', function () 222 { 223 var dic_name = doc.getById('dic_name').getValue(); 224 if ( !dic_name ) 225 { 226 dic_error_message(" Dictionary name should not be empty. "); 227 return false; 228 } 229 //apply handler 230 dic[ this.getId() ].apply( null, [ this, dic_name, dic_buttons ] ); 231 232 return true; 233 }); 234 } 406 if ( dic_button ) 407 dic_button.on( 'click', onDicButtonClick, this ); 408 } 235 409 }; 410 236 411 var reload = function() 237 412 { 238 413 var dialog = this; … … 259 434 } 260 435 261 436 // * user dictionary 262 scayt.getNameUserDictionary( 437 if ( userDicActive ){ 438 scayt.getNameUserDictionary( 263 439 function( o ) 264 440 { 265 441 var dic_name = o.dname; … … 273 449 274 450 }, 275 451 function () 276 { 277 doc.getById( 'dic_name' ).setValue(""); 278 }); 279 280 dic_success_message(""); 452 { 453 doc.getById( 'dic_name' ).setValue(""); 454 }); 455 dic_success_message(""); 456 } 457 281 458 }; 282 459 283 460 function dic_error_message ( m ) 284 461 { 285 462 doc.getById('dic_message').setHtml('<span style="color:red;">' + m + '</span>' ); 286 463 } 287 464 function dic_success_message ( m ) 288 465 { 289 466 doc.getById('dic_message').setHtml('<span style="color:blue;">' + m + '</span>') ; 290 467 } 291 468 function display_dic_buttons ( sIds ) 292 469 { 293 470 294 sIds = newString( sIds );471 sIds = String( sIds ); 295 472 var aIds = sIds.split(','); 296 473 for ( var i=0, l = aIds.length; i < l ; i+=1) 297 474 { … … 301 478 } 302 479 function hide_dic_buttons ( sIds ) 303 480 { 304 sIds = newString( sIds );481 sIds = String( sIds ); 305 482 var aIds = sIds.split(','); 306 483 for ( var i = 0, l = aIds.length; i < l ; i += 1 ) 307 484 { … … 313 490 doc.getById('dic_name').$.value= dic_name; 314 491 } 315 492 316 return { 317 title : editor.lang.scayt.title, 318 minWidth : 340, 319 minHeight : 200, 320 onShow : function() 321 { 322 var dialog = this; 323 dialog.data = editor.fire( 'scaytDialog', {} ); 324 dialog.options = dialog.data.scayt_control.option(); 325 dialog.sLang = dialog.data.scayt_control.sLang; 326 327 if ( !dialog.data || !dialog.data.scayt || !dialog.data.scayt_control ) 328 { 329 alert( 'Error loading application service' ); 330 dialog.hide(); 331 return; 332 } 333 334 var stop = 0; 335 if ( firstLoad ) 336 { 337 dialog.data.scayt.getCaption( 'en', function( caps ) 338 { 339 if ( stop++ > 0 ) // Once only 340 return; 341 captions = caps; 342 init_with_captions.apply( dialog ); 343 reload.apply( dialog ); 344 firstLoad = false; 345 }); 346 } 347 else 348 reload.apply( dialog ); 349 350 dialog.selectPage( dialog.data.tab ); 351 }, 352 onOk : function() 353 { 354 var scayt_control = this.data.scayt_control, 355 o = scayt_control.option(), 356 c = 0; 357 358 // Set up options if any was set. 359 for ( var oN in this.options ) 360 { 361 if (o[oN] != this.options[ oN ] && c === 0 ) 362 { 363 scayt_control.option( this.options ); 364 c++; 365 } 366 } 367 368 // Setup languge if it was changed. 369 var csLang = this.chosed_lang; 370 if ( csLang && this.data.sLang != csLang ) 371 { 372 scayt_control.setLang( csLang ); 373 c++; 374 } 375 if ( c > 0 ) 376 scayt_control.refresh(); 377 }, 378 contents : [ 379 { 380 id : 'options', 381 label : editor.lang.scayt.optionsTab, 382 elements : [ 383 { 384 type : 'html', 385 id : 'options', 386 html : '<div class="inner_options">' + 387 ' <div class="messagebox"></div>' + 388 ' <div style="display:none;">' + 389 ' <input type="checkbox" value="0" id="allCaps" />' + 390 ' <label for="allCaps" id="label_allCaps"></label>' + 391 ' </div>' + 392 ' <div style="display:none;">' + 393 ' <input type="checkbox" value="0" id="ignoreDomainNames" />' + 394 ' <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>' + 395 ' </div>' + 396 ' <div style="display:none;">' + 397 ' <input type="checkbox" value="0" id="mixedCase" />' + 398 ' <label for="mixedCase" id="label_mixedCase"></label>' + 399 ' </div>' + 400 ' <div style="display:none;">' + 401 ' <input type="checkbox" value="0" id="mixedWithDigits" />' + 402 ' <label for="mixedWithDigits" id="label_mixedWithDigits"></label>' + 403 ' </div>' + 404 '</div>' 405 } 406 ] 407 }, 408 { 409 id : 'langs', 410 label : editor.lang.scayt.languagesTab, 411 elements : [ 412 { 413 type : 'html', 414 id : 'langs', 415 html : '<div class="inner_langs">' + 416 ' <div class="messagebox"></div> ' + 417 ' <div style="float:left;width:47%;margin-left:5px;" id="scayt_lcol" ></div>' + 418 ' <div style="float:left;width:47%;margin-left:15px;" id="scayt_rcol"></div>' + 419 '</div>' 420 } 421 ] 422 }, 423 { 424 id : 'dictionaries', 425 label : editor.lang.scayt.dictionariesTab, 426 elements : [ 427 { 428 type : 'html', 429 style: '', 430 id : 'dic', 431 html : '<div class="inner_dictionary" style="text-align:left; white-space:normal;">' + 432 ' <div style="margin:5px auto; width:80%;white-space:normal; overflow:hidden;" id="dic_message"> </div>' + 433 ' <div style="margin:5px auto; width:80%;white-space:normal;"> ' + 434 ' <span class="cke_dialog_ui_labeled_label" >Dictionary name</span><br>'+ 435 ' <span class="cke_dialog_ui_labeled_content" >'+ 436 ' <div class="cke_dialog_ui_input_text">'+ 437 ' <input id="dic_name" type="text" class="cke_dialog_ui_input_text"/>'+ 438 ' </div></span></div>'+ 439 ' <div style="margin:5px auto; width:80%;white-space:normal;">'+ 440 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_create">'+ 441 ' </a>' + 442 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_delete">'+ 443 ' </a>' + 444 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_rename">'+ 445 ' </a>' + 446 ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_restore">'+ 447 ' </a>' + 448 ' </div>' + 449 ' <div style="margin:5px auto; width:95%;white-space:normal;" id="dic_info"></div>' + 450 '</div>' 451 } 452 ] 453 }, 454 { 455 id : 'about', 456 label : editor.lang.scayt.aboutTab, 457 elements : [ 458 { 459 type : 'html', 460 id : 'about', 461 style : 'margin: 10px 40px;', 462 html : '<div id="scayt_about"></div>' 463 } 464 ] 465 } 466 ] 467 }; 468 }); 493 return dialogDefiniton; 494 }); -
_source/plugins/scayt/plugin.js
18 18 var onEngineLoad = function() 19 19 { 20 20 var editor = this; 21 dojo.requireLocalization( 'scayt', 'caption', '', 'ROOT' );22 21 23 22 var createInstance = function() // Create new instance every time Document is created. 24 23 { 25 24 // Initialise Scayt instance. 26 var oParams = CKEDITOR.config.scaytParams ||{};25 var oParams = {}; 27 26 oParams.srcNodeRef = editor.document.getWindow().$.frameElement; // Get the iframe. 28 27 // syntax : AppName.AppVersion@AppRevision 29 28 oParams.assocApp = "CKEDITOR." + CKEDITOR.version + "@" + CKEDITOR.revision; 29 30 oParams.customerid = editor.config.scayt_customerid || "1:11111111111111111111111111111111111111"; 31 oParams.customDictionaryName = editor.config.scayt_customDictionaryName; 32 oParams.userDictionaryName = editor.config.scayt_userDictionaryName; 33 oParams.defLang = editor.scayt_defLang; 34 35 if ( CKEDITOR._scaytParams ) 36 for ( var k in CKEDITOR._scaytParams ) 37 { 38 oParams[ k ] = CKEDITOR._scaytParams[ k ]; 39 } 40 30 41 var scayt_control = new scayt( oParams ); 31 42 32 43 // Copy config. … … 37 48 scayt_control.option( lastInstance.option() ); 38 49 scayt_control.paused = lastInstance.paused; 39 50 } 40 51 41 52 plugin.instances[ editor.name ] = scayt_control; 42 53 43 54 try { … … 67 78 68 79 editor.on( 'beforeCommandExec', function( ev ) // Disable SCAYT before Source command execution. 69 80 { 70 if ( ev.data.name == 'source'&& editor.mode == 'wysiwyg' )81 if ( (ev.data.name == 'source' || ev.data.name == 'newpage') && editor.mode == 'wysiwyg' ) 71 82 { 72 var scayt = plugin.getScayt( editor );73 if ( scayt )83 var scayt_instanse = plugin.getScayt( editor ); 84 if ( scayt_instanse ) 74 85 { 75 scayt.paused = !scayt.disabled; 76 scayt.setDisabled( true ); 86 scayt_instanse.paused = !scayt_instanse.disabled; 87 scayt_instanse.destroy(); 88 delete plugin.instances[ editor.name ]; 77 89 } 78 90 } 79 91 }); 80 92 81 93 // Listen to data manipulation to reflect scayt markup. 82 94 editor.on( 'afterSetData', function() 83 95 { … … 85 97 plugin.getScayt( editor ).refresh(); 86 98 }); 87 99 100 // Reload spell-checking for current word after insertion completed. 101 editor.on( 'insertElement', function() 102 { 103 var scayt_instance = plugin.getScayt( editor ); 104 if ( plugin.isScaytEnabled( editor ) ) 105 { 106 // Unlock the selection before reload, SCAYT will take 107 // care selection update. 108 if ( CKEDITOR.env.ie ) 109 editor.getSelection().unlock( true ); 110 111 // Swallow any SCAYT engine errors. 112 try{ 113 scayt_instance.refresh(); 114 }catch( er ) 115 {} 116 } 117 }, this, null, 50 ); 118 88 119 editor.on( 'scaytDialog', function( ev ) // Communication with dialog. 89 120 { 90 121 ev.data.djConfig = djConfig; … … 134 165 }, 135 166 isScaytEnabled : function( editor ) 136 167 { 137 var scayt = this.getScayt( editor );138 return ( scayt ) ? scayt.disabled === false : false;168 var scayt_instanse = this.getScayt( editor ); 169 return ( scayt_instanse ) ? scayt_instanse.disabled === false : false; 139 170 }, 140 171 loadEngine : function( editor ) 141 172 { … … 154 185 0 ); // First to run. 155 186 156 187 this.engineLoaded = -1; // Loading in progress. 157 // assign diojo configurable vars158 var parseUrl = function(data)159 {160 var m = data.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/);161 return { path: m[1], file: m[2] };162 };163 188 164 189 // compose scayt url 165 190 var protocol = document.location.protocol; 166 var baseUrl = "svc.spellchecker.net/spellcheck/lf/scayt/scayt.js"; 167 var scaytUrl = editor.config.scaytParams.srcScayt || 168 (protocol + "//" + baseUrl); 169 var scaytConfigBaseUrl = parseUrl(scaytUrl).path + "/"; 170 171 djScaytConfig = 191 // Default to 'http' for unknown. 192 protocol = protocol.search( /https?:/) != -1? protocol : 'http:'; 193 var baseUrl = "svc.spellchecker.net/spellcheck/lf/scayt/scayt1.js"; 194 195 var scaytUrl = editor.config.scayt_srcUrl || ( protocol + "//" + baseUrl ); 196 var scaytConfigBaseUrl = plugin.parseUrl( scaytUrl ).path + "/"; 197 198 CKEDITOR._djScaytConfig = 172 199 { 173 200 baseUrl: scaytConfigBaseUrl, 174 201 addOnLoad: … … 193 220 ); 194 221 195 222 return null; 223 }, 224 parseUrl : function ( data ) 225 { 226 var match; 227 if ( data.match && ( match = data.match(/(.*)[\/\\](.*?\.\w+)$/) ) ) 228 return { path: match[1], file: match[2] } 229 else 230 return data; 196 231 } 197 232 }; 198 233 … … 311 346 } 312 347 }); 313 348 314 editor.ui.add( 'Scayt', CKEDITOR.UI_MENUBUTTON,315 {316 label : editor.lang.scayt.title,317 title : editor.lang.scayt.title,318 className : 'cke_button_scayt',319 onRender: function()320 {349 editor.ui.add( 'Scayt', CKEDITOR.UI_MENUBUTTON, 350 { 351 label : editor.lang.scayt.title, 352 title : editor.lang.scayt.title, 353 className : 'cke_button_scayt', 354 onRender: function() 355 { 321 356 command.on( 'state', function() 322 357 { 323 358 this.setState( command.state ); … … 330 365 331 366 editor.getMenuItem( 'scaytToggle' ).label = editor.lang.scayt[ isEnabled ? 'disable' : 'enable' ]; 332 367 333 return {334 scaytToggle : CKEDITOR.TRISTATE_OFF,335 scaytOptions : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,336 scaytLangs : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,337 scaytAbout : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED338 };339 }340 });368 return { 369 scaytToggle : CKEDITOR.TRISTATE_OFF, 370 scaytOptions : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, 371 scaytLangs : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, 372 scaytAbout : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED 373 }; 374 } 375 }); 341 376 342 377 // If the "contextmenu" plugin is loaded, register the listeners. 343 378 if ( editor.contextMenu && editor.addMenuItems ) 344 379 { 345 380 editor.contextMenu.addListener( function( element, selection ) 346 381 { 347 var scayt_control = plugin.getScayt( editor ); 348 if ( !plugin.isScaytEnabled( editor ) || !element || !element.$ ) 382 if ( !( plugin.isScaytEnabled( editor ) && element ) ) 349 383 return null; 350 384 351 var word = scayt_control.getWord( element.$ ); 385 var scayt_control = plugin.getScayt( editor ), 386 word = scayt_control.getWord( element.$ ); 352 387 353 388 if ( !word ) 354 389 return null; … … 450 485 mainSuggestions[ 'scayt_ignore_all' ] = CKEDITOR.TRISTATE_OFF; 451 486 mainSuggestions[ 'scayt_add_word' ] = CKEDITOR.TRISTATE_OFF; 452 487 453 // ** ahow ads entry point 454 // ** hide ads listener register 455 // try{ 456 //scayt_control.showBanner( editor ) 457 // }catch(err){} 488 if ( scayt_control.fireOnContextMenu ) 489 scayt_control.fireOnContextMenu( editor ) 458 490 459 491 return mainSuggestions; 460 492 }); … … 476 508 }); 477 509 })(); 478 510 479 CKEDITOR.config.scaytParams = CKEDITOR.config.scaytParams || {}; 480 CKEDITOR.config.scayt_maxSuggestions = 5; 511 CKEDITOR.config.scayt_maxSuggestions = 5; 481 512 CKEDITOR.config.scayt_autoStartup = false; -
CHANGES.html
117 117 <li><a href="http://dev.fckeditor.net/ticket/3887">#3887</a> : Fixed an issue in which the create 118 118 list command may leak outside of a selected table cell and into the rest of document.</li> 119 119 <li><a href="http://dev.fckeditor.net/ticket/3916">#3916</a> : Fixed maximize does not enlarge editor width when width is set.</li> 120 <li><a href="http://dev.fckeditor.net/ticket/3839">#3839</a> : Update Scayt plugin to reflect the latest change from SpellChecker.net.</li> 120 121 </ul> 121 122 <h3> 122 123 CKEditor 3.0 RC</h3>