1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 /** 7 * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing 8 * mode, which handles the main editing area space. 9 */ 10 11 (function() 12 { 13 // Matches all self-closing tags that are not defined as empty elements in 14 // the DTD (like <span/>). 15 var invalidSelfCloseTagsRegex = /(<(?!br|hr|base|meta|link|param|img|area|input|col)([a-zA-Z0-9:]+)[^>]*)\/>/gi; 16 17 // #### protectEvents - START 18 19 // Matches all tags that have event attributes (onXYZ). 20 var tagsWithEventRegex = /<[^\>]+ on\w+\s*=[\s\S]+?\>/g; 21 22 // Matches all event attributes. 23 var eventAttributesRegex = /\s(on\w+)(?=\s*=\s*?('|")[\s\S]*?\2)/g; 24 25 // Matches the protected attribute prefix. 26 var protectedEventsRegex = /_cke_pa_/g; 27 28 var protectEvents = function( html ) 29 { 30 return html.replace( tagsWithEventRegex, protectEvents_ReplaceTags ); 31 }; 32 33 var protectEvents_ReplaceTags = function( tagMatch ) 34 { 35 // Appends the "_cke_pa_" prefix to the event name. 36 return tagMatch.replace( eventAttributesRegex, ' _cke_pa_$1' ); 37 }; 38 39 var protectEventsRestore = function( html ) 40 { 41 return html.replace( protectedEventsRegex, '' ) ; 42 }; 43 44 // #### protectEvents - END 45 46 // #### protectAttributes - START 47 var protectUrlTagRegex = /<(?:a|area|img)(?=\s).*?\s(?:href|src)=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi, 48 protectUrlAttributeRegex = /\s(href|src)(\s*=\s*?('|")[\s\S]*?\3)/gi, 49 protectedUrlTagRegex = /<(?:a|area|img)(?=\s)(?:"[^"]*"|'[^']*'|[^<])*>/gi, 50 protectedAttributeRegex = /_cke_saved_/gi, 51 protectUrls = function( html ) 52 { 53 return html.replace( protectUrlTagRegex, protectUrls_ReplaceTags ); 54 }, 55 protectUrls_ReplaceTags = function( tagMatch ) 56 { 57 return tagMatch.replace( protectUrlAttributeRegex, '$& _cke_saved_$1$2'); 58 }, 59 protectUrlsRestore = function( html ) 60 { 61 return html.replace( protectedUrlTagRegex, protectUrlsRestore_ReplaceTags ); 62 }, 63 protectUrlsRestore_ReplaceTags = function( tagMatch ) 64 { 65 return tagMatch.replace( protectUrlAttributeRegex, '' ).replace( protectedAttributeRegex, '' ); 66 }; 67 // #### protectAttributes - END 68 69 var onInsertHtml = function( evt ) 70 { 71 if ( this.mode == 'wysiwyg' ) 72 { 73 var $doc = this.document.$; 74 75 if ( CKEDITOR.env.ie ) 76 $doc.selection.createRange().pasteHTML( evt.data ); 77 else 78 $doc.execCommand( 'inserthtml', false, evt.data ); 79 } 80 }; 81 82 var onInsertElement = function( evt ) 83 { 84 if ( this.mode == 'wysiwyg' ) 85 { 86 var element = evt.data, 87 isBlock = CKEDITOR.dtd.$block[ element.getName() ]; 88 89 var selection = this.getSelection(), 90 ranges = selection.getRanges(); 91 92 var range, clone, lastElement, bookmark; 93 94 for ( var i = ranges.length - 1 ; i >= 0 ; i-- ) 95 { 96 range = ranges[ i ]; 97 98 // Remove the original contents. 99 range.deleteContents(); 100 101 clone = element.clone( true ); 102 103 // If the new node is a block element, split the current block. 104 if ( this.config.enterMode != 'br' && isBlock ) 105 range.splitBlock(); 106 107 // Insert the new node. 108 range.insertNode( clone ); 109 110 // Save the last element reference so we can make the 111 // selection later. 112 if ( !lastElement ) 113 lastElement = clone; 114 } 115 116 range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END ); 117 selection.selectRanges( [ range ] ); 118 } 119 }; 120 121 CKEDITOR.plugins.add( 'wysiwygarea', 122 { 123 requires : [ 'editingblock' ], 124 125 init : function( editor, pluginPath ) 126 { 127 editor.on( 'editingBlockReady', function() 128 { 129 var mainElement, 130 iframe, 131 isLoadingData, 132 isPendingFocus, 133 fireMode; 134 135 // The following information is needed for IE only. 136 var isCustomDomain = CKEDITOR.env.ie && document.domain != window.location.hostname; 137 138 // Creates the iframe that holds the editable document. 139 var createIFrame = function() 140 { 141 if ( iframe ) 142 iframe.remove(); 143 144 iframe = new CKEDITOR.dom.element( 'iframe' ) 145 .setAttributes({ 146 frameBorder : 0, 147 allowTransparency : true }) 148 .setStyles({ 149 width : '100%', 150 height : '100%' }); 151 152 if ( CKEDITOR.env.ie ) 153 { 154 if ( isCustomDomain ) 155 { 156 // The document domain must be set within the src 157 // attribute. 158 iframe.setAttribute( 'src', 159 'javascript:void( (function(){' + 160 'document.open();' + 161 'document.domain="' + document.domain + '";' + 162 'document.write( window.parent._cke_htmlToLoad_' + editor.name + ' );' + 163 'document.close();' + 164 'window.parent._cke_htmlToLoad_' + editor.name + ' = null;' + 165 '})() )' ); 166 } 167 else 168 // To avoid HTTPS warnings. 169 iframe.setAttribute( 'src', 'javascript:void(0)' ); 170 } 171 172 // Append the new IFRAME to the main element. For IE, it 173 // must be done after setting the "src", to avoid the 174 // "secure/unsecure" message under HTTPS. 175 mainElement.append( iframe ); 176 }; 177 178 // The script that is appended to the data being loaded. It 179 // enables editing, and makes some 180 var activationScript = 181 '<script id="cke_actscrpt" type="text/javascript">' + 182 'window.onload = function()' + 183 '{' + 184 // Remove this script from the DOM. 185 'var s = document.getElementById( "cke_actscrpt" );' + 186 's.parentNode.removeChild( s );' + 187 188 // Call the temporary function for the editing 189 // boostrap. 190 'window.parent.CKEDITOR._.contentDomReady' + editor.name + '( window );' + 191 '}' + 192 '</script>'; 193 194 // Editing area bootstrap code. 195 var contentDomReady = function( domWindow ) 196 { 197 delete CKEDITOR._[ 'contentDomReady' + editor.name ]; 198 199 var domDocument = domWindow.document, 200 body = domDocument.body; 201 202 body.spellcheck = !editor.config.disableNativeSpellChecker; 203 204 if ( CKEDITOR.env.ie ) 205 { 206 // Don't display the focus border. 207 body.hideFocus = true; 208 209 // Disable and re-enable the body to avoid IE from 210 // taking the editing focus at startup. (#141 / #523) 211 body.disabled = true; 212 body.contentEditable = true; 213 body.removeAttribute( 'disabled' ); 214 } 215 else 216 domDocument.designMode = 'on'; 217 218 // IE, Opera and Safari may not support it and throw 219 // errors. 220 try { domDocument.execCommand( 'enableObjectResizing', false, !editor.config.disableObjectResizing ) ; } catch(e) {} 221 try { domDocument.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ) ; } catch(e) {} 222 223 domWindow = editor.window = new CKEDITOR.dom.window( domWindow ); 224 domDocument = editor.document = new CKEDITOR.dom.document( domDocument ); 225 226 var focusTarget = ( CKEDITOR.env.ie || CKEDITOR.env.safari ) ? 227 domWindow : domDocument; 228 229 focusTarget.on( 'blur', function() 230 { 231 editor.focusManager.blur(); 232 }); 233 234 focusTarget.on( 'focus', function() 235 { 236 editor.focusManager.focus(); 237 }); 238 239 var keystrokeHandler = editor.keystrokeHandler; 240 if ( keystrokeHandler ) 241 keystrokeHandler.attach( domDocument ); 242 243 editor.fire( 'contentDom' ); 244 245 if ( fireMode ) 246 { 247 editor.mode = 'wysiwyg'; 248 editor.fire( 'mode' ); 249 fireMode = false; 250 } 251 252 isLoadingData = false; 253 254 if ( isPendingFocus ) 255 editor.focus(); 256 }; 257 258 editor.addMode( 'wysiwyg', 259 { 260 load : function( holderElement, data, isSnapshot ) 261 { 262 mainElement = holderElement; 263 264 // Create the iframe at load for all browsers 265 // except FF and IE with custom domain. 266 if ( !isCustomDomain || !CKEDITOR.env.gecko ) 267 createIFrame(); 268 269 // The editor data "may be dirty" after this 270 // point. 271 editor.mayBeDirty = true; 272 273 fireMode = true; 274 275 if ( isSnapshot ) 276 this.loadSnapshotData( data ); 277 else 278 this.loadData( data ); 279 }, 280 281 loadData : function( data ) 282 { 283 isLoadingData = true; 284 285 // Get the HTML version of the data. 286 if ( editor.dataProcessor ) 287 data = editor.dataProcessor.toHtml( data ); 288 289 // Fix for invalid self-closing tags (see #152). 290 // TODO: Check if this fix is really needed as 291 // soon as we have the XHTML generator. 292 if ( CKEDITOR.env.ie ) 293 data = data.replace( invalidSelfCloseTagsRegex, '$1></$2>' ); 294 295 // Prevent event attributes (like "onclick") to 296 // execute while editing. 297 if ( CKEDITOR.env.ie || CKEDITOR.env.webkit ) 298 data = protectEvents( data ); 299 300 // Protect src or href attributes. 301 data = protectUrls( data ); 302 303 // Replace tags with fake elements. 304 if ( editor.fakeobjects ) 305 data = editor.fakeobjects.protectHtml( data ); 306 307 data = 308 editor.config.docType + 309 '<html dir="' + editor.config.contentsLangDirection + '">' + 310 '<head>' + 311 '<link href="' + editor.config.contentsCss + '" type="text/css" rel="stylesheet" _fcktemp="true"/>' + 312 '</head>' + 313 '<body>' + 314 data + 315 '</body>' + 316 '</html>' + 317 activationScript; 318 319 // For custom domain in IE, set the global variable 320 // that will temporarily hold the editor data. This 321 // reference will be used in the ifram src. 322 if ( isCustomDomain ) 323 window[ '_cke_htmlToLoad_' + editor.name ] = data; 324 325 CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady; 326 327 // We need to recreate the iframe in FF for every 328 // data load, otherwise the following spellcheck 329 // and execCommand features will be active only for 330 // the first time. 331 // The same is valid for IE with custom domain, 332 // because the iframe src must be reset every time. 333 if ( isCustomDomain || CKEDITOR.env.gecko ) 334 createIFrame(); 335 336 // For custom domain in IE, the data loading is 337 // done through the src attribute of the iframe. 338 if ( !isCustomDomain ) 339 { 340 var doc = iframe.$.contentWindow.document; 341 doc.open(); 342 doc.write( data ); 343 doc.close(); 344 } 345 }, 346 347 getData : function() 348 { 349 var data = iframe.$.contentWindow.document.body; 350 351 if ( editor.dataProcessor ) 352 data = editor.dataProcessor.toDataFormat( new CKEDITOR.dom.element( data ) ); 353 else 354 data = data.innerHTML; 355 356 // Restore protected attributes. 357 data = protectEventsRestore( data ); 358 359 // Restore protected URLs. 360 data = protectUrlsRestore( data ); 361 362 // Restore fake elements. 363 if ( editor.fakeobjects ) 364 data = editor.fakeobjects.restoreHtml( data ); 365 366 return data; 367 }, 368 369 getSnapshotData : function() 370 { 371 return iframe.$.contentWindow.document.body.innerHTML; 372 }, 373 374 loadSnapshotData : function( data ) 375 { 376 iframe.$.contentWindow.document.body.innerHTML = data; 377 }, 378 379 unload : function( holderElement ) 380 { 381 editor.window = editor.document = iframe = mainElement = isPendingFocus = null; 382 383 editor.fire( 'contentDomUnload' ); 384 }, 385 386 focus : function() 387 { 388 if ( isLoadingData ) 389 isPendingFocus = true; 390 else if ( editor.window ) 391 editor.window.focus(); 392 } 393 }); 394 395 editor.on( 'insertHtml', onInsertHtml, null, null, 20 ); 396 editor.on( 'insertElement', onInsertElement, null, null, 20 ); 397 }); 398 } 399 }); 400 })(); 401 402 /** 403 * Disables the ability of resize objects (image and tables) in the editing 404 * area 405 * @type Boolean 406 * @default false 407 * @example 408 * config.disableObjectResizing = true; 409 */ 410 CKEDITOR.config.disableObjectResizing = false; 411 412 /** 413 * Disables the "table tools" offered natively by the browser (currently 414 * Firefox only) to make quick table editing operations, like adding or 415 * deleting rows and columns. 416 * @type Boolean 417 * @default true 418 * @example 419 * config.disableNativeTableHandles = false; 420 */ 421 CKEDITOR.config.disableNativeTableHandles = true; 422 423 /** 424 * Disables the built-in spell checker while typing natively available in the 425 * browser (currently Firefox and Safari only).<br /><br /> 426 * 427 * Even if word suggestions will not appear in the FCKeditor context menu, this 428 * feature is useful to help quickly identifying misspelled words.<br /><br /> 429 * 430 * This setting is currently compatible with Firefox only due to limitations in 431 * other browsers. 432 * @type Boolean 433 * @default true 434 * @example 435 * config.disableNativeSpellChecker = false; 436 */ 437 CKEDITOR.config.disableNativeSpellChecker = true; 438