1/* 2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net 3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben 4 * 5 * == BEGIN LICENSE == 6 * 7 * Licensed under the terms of any of the following licenses at your 8 * choice: 9 * 10 * - GNU General Public License Version 2 or later (the "GPL") 11 * http://www.gnu.org/licenses/gpl.html 12 * 13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") 14 * http://www.gnu.org/licenses/lgpl.html 15 * 16 * - Mozilla Public License Version 1.1 or later (the "MPL") 17 * http://www.mozilla.org/MPL/MPL-1.1.html 18 * 19 * == END LICENSE == 20 * 21 * Handles styles in a give document. 22 */ 23 24var FCKStyles = FCK.Styles = 25{ 26 _Callbacks : {}, 27 _ObjectStyles : {}, 28 29 ApplyStyle : function( style ) 30 { 31 if ( typeof style == 'string' ) 32 style = this.GetStyles()[ style ] ; 33 34 if ( style ) 35 { 36 if ( style.GetType() == FCK_STYLE_OBJECT ) 37 style.ApplyToObject( FCKSelection.GetSelectedElement() ) ; 38 else 39 style.ApplyToSelection( FCK.EditorWindow ) ; 40 41 FCK.Events.FireEvent( 'OnSelectionChange' ) ; 42 } 43 }, 44 45 RemoveStyle : function( style ) 46 { 47 if ( typeof style == 'string' ) 48 style = this.GetStyles()[ style ] ; 49 50 if ( style ) 51 { 52 style.RemoveFromSelection( FCK.EditorWindow ) ; 53 FCK.Events.FireEvent( 'OnSelectionChange' ) ; 54 } 55 }, 56 57 /** 58 * Defines a callback function to be called when the current state of a 59 * specific style changes. 60 */ 61 AttachStyleStateChange : function( styleName, callback, callbackOwner ) 62 { 63 var callbacks = this._Callbacks[ styleName ] ; 64 65 if ( !callbacks ) 66 callbacks = this._Callbacks[ styleName ] = [] ; 67 68 callbacks.push( [ callback, callbackOwner ] ) ; 69 }, 70 71 CheckSelectionChanges : function() 72 { 73 var startElement = FCKSelection.GetBoundaryParentElement( true ) ; 74 75 if ( !startElement ) 76 return ; 77 78 // Walks the start node parents path, checking all styles that are being listened. 79 var path = new FCKElementPath( startElement ) ; 80 var styles = this.GetStyles() ; 81 82 for ( var styleName in styles ) 83 { 84 var callbacks = this._Callbacks[ styleName ] ; 85 86 if ( callbacks ) 87 { 88 var style = styles[ styleName ] ; 89 var state = style.CheckActive( path ) ; 90 91 if ( style._LastState != state ) 92 { 93 style._LastState = state ; 94 95 for ( var i = 0 ; i < callbacks.length ; i++ ) 96 { 97 var callback = callbacks[i][0] ; 98 var callbackOwner = callbacks[i][1] ; 99 100 callback.call( callbackOwner || window, styleName, state ) ; 101 } 102 } 103 } 104 } 105 }, 106 107 CheckStyleInSelection : function( styleName ) 108 { 109 return false ; 110 }, 111 112 _GetRemoveFormatTagsRegex : function () 113 { 114 var regex = new RegExp( '^(?:' + FCKConfig.RemoveFormatTags.replace( /,/g,'|' ) + ')$', 'i' ) ; 115 116 return (this._GetRemoveFormatTagsRegex = function() 117 { 118 return regex ; 119 }) 120 && regex ; 121 }, 122 123 /** 124 * Remove all styles from the current selection. 125 * TODO: 126 * - This is almost a duplication of FCKStyle.RemoveFromRange. We should 127 * try to merge things. 128 */ 129 RemoveAll : function() 130 { 131 var range = new FCKDomRange( FCK.EditorWindow ) ; 132 range.MoveToSelection() ; 133 134 if ( range.CheckIsCollapsed() ) 135 return ; 136 137 // Expand the range, if inside inline element boundaries. 138 range.Expand( 'inline_elements' ) ; 139 140 // Get the bookmark nodes. 141 // Bookmark the range so we can re-select it after processing. 142 var bookmark = range.CreateBookmark( true ) ; 143 144 // The style will be applied within the bookmark boundaries. 145 var startNode = range.GetBookmarkNode( bookmark, true ) ; 146 var endNode = range.GetBookmarkNode( bookmark, false ) ; 147 148 range.Release( true ) ; 149 150 var tagsRegex = this._GetRemoveFormatTagsRegex() ; 151 152 // We need to check the selection boundaries (bookmark spans) to break 153 // the code in a way that we can properly remove partially selected nodes. 154 // For example, removing a <b> style from 155 // <b>This is [some text</b> to show <b>the] problem</b> 156 // ... where [ and ] represent the selection, must result: 157 // <b>This is </b>[some text to show the]<b> problem</b> 158 // The strategy is simple, we just break the partial nodes before the 159 // removal logic, having something that could be represented this way: 160 // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b> 161 162 // Let's start checking the start boundary. 163 var path = new FCKElementPath( startNode ) ; 164 var pathElements = path.Elements ; 165 var pathElement ; 166 167 for ( var i = 1 ; i < pathElements.length ; i++ ) 168 { 169 pathElement = pathElements[i] ; 170 171 if ( pathElement == path.Block || pathElement == path.BlockLimit ) 172 break ; 173 174 // If this element can be removed (even partially). 175 if ( tagsRegex.test( pathElement.nodeName ) ) 176 FCKDomTools.BreakParent( startNode, pathElement, range ) ; 177 } 178 179 // Now the end boundary. 180 path = new FCKElementPath( endNode ) ; 181 pathElements = path.Elements ; 182 183 for ( var i = 1 ; i < pathElements.length ; i++ ) 184 { 185 pathElement = pathElements[i] ; 186 187 if ( pathElement == path.Block || pathElement == path.BlockLimit ) 188 break ; 189 190 elementName = pathElement.nodeName.toLowerCase() ; 191 192 // If this element can be removed (even partially). 193 if ( tagsRegex.test( pathElement.nodeName ) ) 194 FCKDomTools.BreakParent( endNode, pathElement, range ) ; 195 } 196 197 // Navigate through all nodes between the bookmarks. 198 var currentNode = FCKDomTools.GetNextSourceNode( startNode, true, 1 ) ; 199 200 while ( currentNode ) 201 { 202 // If we have reached the end of the selection, stop looping. 203 if ( currentNode == endNode ) 204 break ; 205 206 // Cache the next node to be processed. Do it now, because 207 // currentNode may be removed. 208 var nextNode = FCKDomTools.GetNextSourceNode( currentNode, false, 1 ) ; 209 210 // Remove elements nodes that match with this style rules. 211 if ( tagsRegex.test( currentNode.nodeName ) ) 212 FCKDomTools.RemoveNode( currentNode, true ) ; 213 214 currentNode = nextNode ; 215 } 216 217 range.SelectBookmark( bookmark ) ; 218 219 FCK.Events.FireEvent( 'OnSelectionChange' ) ; 220 }, 221 222 GetStyle : function( styleName ) 223 { 224 return this.GetStyles()[ styleName ] ; 225 }, 226 227 GetStyles : function() 228 { 229 var styles = this._GetStyles ; 230 if ( !styles ) 231 { 232 styles = this._GetStyles = FCKTools.Merge( 233 this._LoadStylesCore(), 234 this._LoadStylesCustom(), 235 this._LoadStylesXml() ) ; 236 } 237 return styles ; 238 }, 239 240 CheckHasObjectStyle : function( elementName ) 241 { 242 return !!this._ObjectStyles[ elementName ] ; 243 }, 244 245 _LoadStylesCore : function() 246 { 247 var styles = {}; 248 var styleDefs = FCKConfig.CoreStyles ; 249 250 for ( var styleName in styleDefs ) 251 { 252 // Core styles are prefixed with _FCK_. 253 var style = styles[ '_FCK_' + styleName ] = new FCKStyle( styleDefs[ styleName ] ) ; 254 style.IsCore = true ; 255 } 256 return styles ; 257 }, 258 259 _LoadStylesCustom : function() 260 { 261 var styles = {}; 262 var styleDefs = FCKConfig.CustomStyles ; 263 264 if ( styleDefs ) 265 { 266 for ( var styleName in styleDefs ) 267 styles[ styleName ] = new FCKStyle( styleDefs[ styleName ] ) ; 268 } 269 270 return styles ; 271 }, 272 273 _LoadStylesXml : function() 274 { 275 var styles = {}; 276 277 var stylesXmlPath = FCKConfig.StylesXmlPath ; 278 279 if ( !stylesXmlPath || stylesXmlPath.length == 0 ) 280 return styles ; 281 282 // Load the XML file into a FCKXml object. 283 var xml = new FCKXml() ; 284 xml.LoadUrl( stylesXmlPath ) ; 285 286 var stylesXmlObj = FCKXml.TransformToObject( xml.SelectSingleNode( 'Styles' ) ) ; 287 288 // Get the "Style" nodes defined in the XML file. 289 var styleNodes = stylesXmlObj.$Style ; 290 291 // Add each style to our "Styles" collection. 292 for ( var i = 0 ; i < styleNodes.length ; i++ ) 293 { 294 var styleNode = styleNodes[i] ; 295 296 var element = ( styleNode.element || '' ).toLowerCase() ; 297 298 if ( element.length == 0 ) 299 throw( 'The element name is required. Error loading "' + stylesXmlPath + '"' ) ; 300 301 var styleDef = { 302 Element : element, 303 Attributes : {}, 304 Styles : {}, 305 Overrides : [] 306 } ; 307 308 // Get the attributes defined for the style (if any). 309 var attNodes = styleNode.$Attribute || [] ; 310 311 // Add the attributes to the style definition object. 312 for ( var j = 0 ; j < attNodes.length ; j++ ) 313 { 314 styleDef.Attributes[ attNodes[j].name ] = attNodes[j].value ; 315 } 316 317 // Get the styles defined for the style (if any). 318 var cssStyleNodes = styleNode.$Style || [] ; 319 320 // Add the attributes to the style definition object. 321 for ( j = 0 ; j < cssStyleNodes.length ; j++ ) 322 { 323 styleDef.Styles[ cssStyleNodes[j].name ] = cssStyleNodes[j].value ; 324 } 325 326 // Load override definitions. 327 var cssStyleOverrideNodes = styleNode.$Override ; 328 if ( cssStyleOverrideNodes ) 329 { 330 for ( j = 0 ; j < cssStyleOverrideNodes.length ; j++ ) 331 { 332 var overrideNode = cssStyleOverrideNodes[j] ; 333 var overrideDef = 334 { 335 Element : overrideNode.element 336 } ; 337 338 var overrideAttNode = overrideNode.$Attribute ; 339 if ( overrideAttNode ) 340 { 341 overrideDef.Attributes = {} ; 342 for ( var k = 0 ; k < overrideAttNode.length ; k++ ) 343 { 344 var overrideAttValue = overrideAttNode[k].value || null ; 345 if ( overrideAttValue ) 346 { 347 // Check if the override attribute value is a regular expression. 348 var regexMatch = overrideAttValue && FCKRegexLib.RegExp.exec( overrideAttValue ) ; 349 if ( regexMatch ) 350 overrideAttValue = new RegExp( regexMatch[1], regexMatch[2] || '' ) ; 351 } 352 overrideDef.Attributes[ overrideAttNode[k].name ] = overrideAttValue ; 353 } 354 } 355 356 styleDef.Overrides.push( overrideDef ) ; 357 } 358 } 359 360 var style = new FCKStyle( styleDef ) ; 361 style.Name = styleNode.name || element ; 362 363 if ( style.GetType() == FCK_STYLE_OBJECT ) 364 this._ObjectStyles[ element ] = true ; 365 366 // Add the style to the "Styles" collection using it's name as the key. 367 styles[ style.Name ] = style ; 368 } 369 370 return styles ; 371 } 372} ; 373