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 * @name CKEDITOR.theme 8 * @class 9 */ 10 11 CKEDITOR.themes.add( 'default', (function() 12 { 13 var hiddenSkins = {}; 14 15 function checkSharedSpace( editor, spaceName ) 16 { 17 var container, 18 element; 19 20 // Try to retrieve the target element from the sharedSpaces settings. 21 element = editor.config.sharedSpaces; 22 element = element && element[ spaceName ]; 23 element = element && CKEDITOR.document.getById( element ); 24 25 // If the element is available, we'll then create the container for 26 // the space. 27 if ( element ) 28 { 29 // Creates an HTML structure that reproduces the editor class hierarchy. 30 var html = 31 '<span class="cke_shared "' + 32 ' dir="'+ editor.lang.dir + '"' + 33 '>' + 34 '<span class="' + editor.skinClass + ' ' + editor.id + ' cke_editor_' + editor.name + '">' + 35 '<span class="' + CKEDITOR.env.cssClass + '">' + 36 '<span class="cke_wrapper cke_' + editor.lang.dir + '">' + 37 '<span class="cke_editor">' + 38 '<div class="cke_' + spaceName + '">' + 39 '</div></span></span></span></span></span>'; 40 41 var mainContainer = element.append( CKEDITOR.dom.element.createFromHtml( html, element.getDocument() ) ); 42 43 // Only the first container starts visible. Others get hidden. 44 if ( element.getCustomData( 'cke_hasshared' ) ) 45 mainContainer.hide(); 46 else 47 element.setCustomData( 'cke_hasshared', 1 ); 48 49 // Get the deeper inner <div>. 50 container = mainContainer.getChild( [0,0,0,0] ); 51 52 // Save a reference to the shared space container. 53 !editor.sharedSpaces && ( editor.sharedSpaces = {} ); 54 editor.sharedSpaces[ spaceName ] = container; 55 56 // When the editor gets focus, we show the space container, hiding others. 57 editor.on( 'focus', function() 58 { 59 for ( var i = 0, sibling, children = element.getChildren() ; ( sibling = children.getItem( i ) ) ; i++ ) 60 { 61 if ( sibling.type == CKEDITOR.NODE_ELEMENT 62 && !sibling.equals( mainContainer ) 63 && sibling.hasClass( 'cke_shared' ) ) 64 { 65 sibling.hide(); 66 } 67 } 68 69 mainContainer.show(); 70 }); 71 72 editor.on( 'destroy', function() 73 { 74 mainContainer.remove(); 75 }); 76 } 77 78 return container; 79 } 80 81 return /** @lends CKEDITOR.theme */ { 82 build : function( editor, themePath ) 83 { 84 var name = editor.name, 85 element = editor.element, 86 elementMode = editor.elementMode; 87 88 if ( !element || elementMode == CKEDITOR.ELEMENT_MODE_NONE ) 89 return; 90 91 if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) 92 element.hide(); 93 94 // Get the HTML for the predefined spaces. 95 var topHtml = editor.fire( 'themeSpace', { space : 'top', html : '' } ).html; 96 var contentsHtml = editor.fire( 'themeSpace', { space : 'contents', html : '' } ).html; 97 var bottomHtml = editor.fireOnce( 'themeSpace', { space : 'bottom', html : '' } ).html; 98 99 var height = contentsHtml && editor.config.height; 100 101 var tabIndex = editor.config.tabIndex || editor.element.getAttribute( 'tabindex' ) || 0; 102 103 // The editor height is considered only if the contents space got filled. 104 if ( !contentsHtml ) 105 height = 'auto'; 106 else if ( !isNaN( height ) ) 107 height += 'px'; 108 109 var style = ''; 110 var width = editor.config.width; 111 112 if ( width ) 113 { 114 if ( !isNaN( width ) ) 115 width += 'px'; 116 117 style += "width: " + width + ";"; 118 } 119 120 var sharedTop = topHtml && checkSharedSpace( editor, 'top' ), 121 sharedBottoms = checkSharedSpace( editor, 'bottom' ); 122 123 sharedTop && ( sharedTop.setHtml( topHtml ) , topHtml = '' ); 124 sharedBottoms && ( sharedBottoms.setHtml( bottomHtml ), bottomHtml = '' ); 125 126 var hideSkin = '<style>.' + editor.skinClass + '{visibility:hidden;}</style>'; 127 if ( hiddenSkins[ editor.skinClass ] ) 128 hideSkin = ''; 129 else 130 hiddenSkins[ editor.skinClass ] = 1; 131 132 var container = CKEDITOR.dom.element.createFromHtml( [ 133 '<span' + 134 ' id="cke_', name, '"' + 135 ' class="', editor.skinClass, ' ', editor.id, ' cke_editor_', name, '"' + 136 ' dir="', editor.lang.dir, '"' + 137 ' title="', ( CKEDITOR.env.gecko ? ' ' : '' ), '"' + 138 ' lang="', editor.langCode, '"' + 139 ( CKEDITOR.env.webkit? ' tabindex="' + tabIndex + '"' : '' ) + 140 ' role="application"' + 141 ' aria-labelledby="cke_', name, '_arialbl"' + 142 ( style ? ' style="' + style + '"' : '' ) + 143 '>' + 144 '<span id="cke_', name, '_arialbl" class="cke_voice_label">' + editor.lang.editor + '</span>' + 145 '<span class="' , CKEDITOR.env.cssClass, '" role="presentation">' + 146 '<span class="cke_wrapper cke_', editor.lang.dir, '" role="presentation">' + 147 '<table class="cke_editor" border="0" cellspacing="0" cellpadding="0" role="presentation"><tbody>' + 148 '<tr', topHtml ? '' : ' style="display:none"', ' role="presentation"><td id="cke_top_' , name, '" class="cke_top" role="presentation">' , topHtml , '</td></tr>' + 149 '<tr', contentsHtml ? '' : ' style="display:none"', ' role="presentation"><td id="cke_contents_', name, '" class="cke_contents" style="height:', height, '" role="presentation">', contentsHtml, '</td></tr>' + 150 '<tr', bottomHtml ? '' : ' style="display:none"', ' role="presentation"><td id="cke_bottom_' , name, '" class="cke_bottom" role="presentation">' , bottomHtml , '</td></tr>' + 151 '</tbody></table>' + 152 //Hide the container when loading skins, later restored by skin css. 153 hideSkin + 154 '</span>' + 155 '</span>' + 156 '</span>' ].join( '' ) ); 157 158 container.getChild( [1, 0, 0, 0, 0] ).unselectable(); 159 container.getChild( [1, 0, 0, 0, 2] ).unselectable(); 160 161 if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) 162 container.insertAfter( element ); 163 else 164 element.append( container ); 165 166 /** 167 * The DOM element that holds the main editor interface. 168 * @name CKEDITOR.editor.prototype.container 169 * @type CKEDITOR.dom.element 170 * @example 171 * var editor = CKEDITOR.instances.editor1; 172 * alert( <b>editor.container</b>.getName() ); "span" 173 */ 174 editor.container = container; 175 176 // Disable browser context menu for editor's chrome. 177 container.disableContextMenu(); 178 179 // Use a class to indicate that the current selection is in different direction than the UI. 180 editor.on( 'contentDirChanged', function( evt ) 181 { 182 var func = ( editor.lang.dir != evt.data ? 'add' : 'remove' ) + 'Class'; 183 184 container.getChild( 1 )[ func ]( 'cke_mixed_dir_content' ); 185 186 // Put the mixed direction class on the respective element also for shared spaces. 187 var toolbarSpace = this.sharedSpaces && this.sharedSpaces[ this.config.toolbarLocation ]; 188 toolbarSpace && toolbarSpace.getParent().getParent()[ func ]( 'cke_mixed_dir_content' ); 189 }); 190 191 editor.fireOnce( 'themeLoaded' ); 192 editor.fireOnce( 'uiReady' ); 193 }, 194 195 buildDialog : function( editor ) 196 { 197 var baseIdNumber = CKEDITOR.tools.getNextNumber(); 198 199 var element = CKEDITOR.dom.element.createFromHtml( [ 200 '<div class="', editor.id, '_dialog cke_editor_', editor.name.replace('.', '\\.'), '_dialog cke_skin_', editor.skinName, 201 '" dir="', editor.lang.dir, '"' + 202 ' lang="', editor.langCode, '"' + 203 ' role="dialog"' + 204 ' aria-labelledby="%title#"' + 205 '>' + 206 '<table class="cke_dialog', ' ' + CKEDITOR.env.cssClass, 207 ' cke_', editor.lang.dir, '" style="position:absolute" role="presentation">' + 208 '<tr><td role="presentation">' + 209 '<div class="%body" role="presentation">' + 210 '<div id="%title#" class="%title" role="presentation"></div>' + 211 '<a id="%close_button#" class="%close_button" href="javascript:void(0)" title="' + editor.lang.common.close+'" role="button"><span class="cke_label">X</span></a>' + 212 '<div id="%tabs#" class="%tabs" role="tablist"></div>' + 213 '<table class="%contents" role="presentation">' + 214 '<tr>' + 215 '<td id="%contents#" class="%contents" role="presentation"></td>' + 216 '</tr>' + 217 '<tr>' + 218 '<td id="%footer#" class="%footer" role="presentation"></td>' + 219 '</tr>' + 220 '</table>' + 221 '</div>' + 222 '<div id="%tl#" class="%tl"></div>' + 223 '<div id="%tc#" class="%tc"></div>' + 224 '<div id="%tr#" class="%tr"></div>' + 225 '<div id="%ml#" class="%ml"></div>' + 226 '<div id="%mr#" class="%mr"></div>' + 227 '<div id="%bl#" class="%bl"></div>' + 228 '<div id="%bc#" class="%bc"></div>' + 229 '<div id="%br#" class="%br"></div>' + 230 '</td></tr>' + 231 '</table>', 232 233 //Hide the container when loading skins, later restored by skin css. 234 ( CKEDITOR.env.ie ? '' : '<style>.cke_dialog{visibility:hidden;}</style>' ), 235 236 '</div>' 237 ].join( '' ) 238 .replace( /#/g, '_' + baseIdNumber ) 239 .replace( /%/g, 'cke_dialog_' ) ); 240 241 var body = element.getChild( [ 0, 0, 0, 0, 0 ] ), 242 title = body.getChild( 0 ), 243 close = body.getChild( 1 ); 244 245 // IFrame shim for dialog that masks activeX in IE. (#7619) 246 if ( CKEDITOR.env.ie && !CKEDITOR.env.ie6Compat ) 247 { 248 var isCustomDomain = CKEDITOR.env.isCustomDomain(), 249 src = 'javascript:void(function(){' + encodeURIComponent( 'document.open();' + ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) + 'document.close();' ) + '}())', 250 iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + 251 ' frameBorder="0"' + 252 ' class="cke_iframe_shim"' + 253 ' src="' + src + '"' + 254 ' tabIndex="-1"' + 255 '></iframe>' ); 256 iframe.appendTo( body.getParent() ); 257 } 258 259 // Make the Title and Close Button unselectable. 260 title.unselectable(); 261 close.unselectable(); 262 263 264 return { 265 element : element, 266 parts : 267 { 268 dialog : element.getChild( 0 ), 269 title : title, 270 close : close, 271 tabs : body.getChild( 2 ), 272 contents : body.getChild( [ 3, 0, 0, 0 ] ), 273 footer : body.getChild( [ 3, 0, 1, 0 ] ) 274 } 275 }; 276 }, 277 278 destroy : function( editor ) 279 { 280 var container = editor.container, 281 element = editor.element; 282 283 if ( container ) 284 { 285 container.clearCustomData(); 286 container.remove(); 287 } 288 289 if ( element ) 290 { 291 element.clearCustomData(); 292 editor.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE && element.show(); 293 delete editor.element; 294 } 295 } 296 }; 297 })() ); 298 299 /** 300 * Returns the DOM element that represents a theme space. The default theme defines 301 * three spaces, namely "top", "contents" and "bottom", representing the main 302 * blocks that compose the editor interface. 303 * @param {String} spaceName The space name. 304 * @returns {CKEDITOR.dom.element} The element that represents the space. 305 * @example 306 * // Hide the bottom space in the UI. 307 * var bottom = editor.getThemeSpace( 'bottom' ); 308 * bottom.setStyle( 'display', 'none' ); 309 */ 310 CKEDITOR.editor.prototype.getThemeSpace = function( spaceName ) 311 { 312 var spacePrefix = 'cke_' + spaceName; 313 var space = this._[ spacePrefix ] || 314 ( this._[ spacePrefix ] = CKEDITOR.document.getById( spacePrefix + '_' + this.name ) ); 315 return space; 316 }; 317 318 /** 319 * Resizes the editor interface. 320 * @param {Number|String} width The new width. It can be an pixels integer or a 321 * CSS size value. 322 * @param {Number|String} height The new height. It can be an pixels integer or 323 * a CSS size value. 324 * @param {Boolean} [isContentHeight] Indicates that the provided height is to 325 * be applied to the editor contents space, not to the entire editor 326 * interface. Defaults to false. 327 * @param {Boolean} [resizeInner] Indicates that the first inner interface 328 * element must receive the size, not the outer element. The default theme 329 * defines the interface inside a pair of span elements 330 * (<span><span>...</span></span>). By default the 331 * first span element receives the sizes. If this parameter is set to 332 * true, the second span is sized instead. 333 * @example 334 * editor.resize( 900, 300 ); 335 * @example 336 * editor.resize( '100%', 450, true ); 337 */ 338 CKEDITOR.editor.prototype.resize = function( width, height, isContentHeight, resizeInner ) 339 { 340 var container = this.container, 341 contents = CKEDITOR.document.getById( 'cke_contents_' + this.name ), 342 contentsFrame = CKEDITOR.env.webkit && this.document && this.document.getWindow().$.frameElement, 343 outer = resizeInner ? container.getChild( 1 ) : container; 344 345 // Set as border box width. (#5353) 346 outer.setSize( 'width', width, true ); 347 348 // WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (#8348) 349 contentsFrame && ( contentsFrame.style.width = '1%' ); 350 351 // Get the height delta between the outer table and the content area. 352 // If we're setting the content area's height, then we don't need the delta. 353 var delta = isContentHeight ? 0 : ( outer.$.offsetHeight || 0 ) - ( contents.$.clientHeight || 0 ); 354 contents.setStyle( 'height', Math.max( height - delta, 0 ) + 'px' ); 355 356 // WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (#8348) 357 contentsFrame && ( contentsFrame.style.width = '100%' ); 358 359 // Emit a resize event. 360 this.fire( 'resize' ); 361 }; 362 363 /** 364 * Gets the element that can be freely used to check the editor size. This method 365 * is mainly used by the resize plugin, which adds a UI handle that can be used 366 * to resize the editor. 367 * @param {Boolean} forContents Whether to return the "contents" part of the theme instead of the container. 368 * @returns {CKEDITOR.dom.element} The resizable element. 369 * @example 370 */ 371 CKEDITOR.editor.prototype.getResizable = function( forContents ) 372 { 373 return forContents ? CKEDITOR.document.getById( 'cke_contents_' + this.name ) : this.container; 374 }; 375 376 /** 377 * Makes it possible to place some of the editor UI blocks, like the toolbar 378 * and the elements path, into any element in the page. 379 * The elements used to hold the UI blocks can be shared among several editor 380 * instances. In that case, only the blocks of the active editor instance will 381 * display. 382 * @name CKEDITOR.config.sharedSpaces 383 * @type Object 384 * @default undefined 385 * @example 386 * // Place the toolbar inside the element with ID "someElementId" and the 387 * // elements path into the element with ID "anotherId". 388 * config.sharedSpaces = 389 * { 390 * top : 'someElementId', 391 * bottom : 'anotherId' 392 * }; 393 * @example 394 * // Place the toolbar inside the element with ID "someElementId". The 395 * // elements path will remain attached to the editor UI. 396 * config.sharedSpaces = 397 * { 398 * top : 'someElementId' 399 * }; 400 */ 401 402 /** 403 * Fired after the editor instance is resized through 404 * the {@link CKEDITOR.editor.prototype.resize} method. 405 * @name CKEDITOR.editor#resize 406 * @event 407 */ 408