/* Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing * mode, which handles the main editing area space. */ (function() { // Matching an empty paragraph at the end of document. var emptyParagraphRegexp = /(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi; var notWhitespaceEval = CKEDITOR.dom.walker.whitespaces( true ); // Elements that could blink the cursor anchoring beside it, like hr, page-break. (#6554) function nonEditable( element ) { return element.isBlockBoundary() && CKEDITOR.dtd.$empty[ element.getName() ]; } function onInsert( insertFunc ) { return function( evt ) { if ( this.mode == 'wysiwyg' ) { this.focus(); this.fire( 'saveSnapshot' ); insertFunc.call( this, evt.data ); // Save snaps after the whole execution completed. // This's a workaround for make DOM modification's happened after // 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents' // call. CKEDITOR.tools.setTimeout( function() { this.fire( 'saveSnapshot' ); }, 0, this ); } }; } function doInsertHtml( data ) { if ( this.dataProcessor ) data = this.dataProcessor.toHtml( data ); if ( !data ) return; // HTML insertion only considers the first range. var selection = this.getSelection(), range = selection.getRanges()[ 0 ]; if ( range.checkReadOnly() ) return; // Opera: force block splitting when pasted content contains block. (#7801) if ( CKEDITOR.env.opera ) { var path = new CKEDITOR.dom.elementPath( range.startContainer ); if ( path.block ) { var nodes = CKEDITOR.htmlParser.fragment.fromHtml( data, false ).children; for ( var i = 0, count = nodes.length; i < count; i++ ) { if ( nodes[ i ]._.isBlockLike ) { range.splitBlock( this.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); range.insertNode( range.document.createText( '' ) ); range.select(); break; } } } } if ( CKEDITOR.env.ie ) { var selIsLocked = selection.isLocked; if ( selIsLocked ) selection.unlock(); var $sel = selection.getNative(); // Delete control selections to avoid IE bugs on pasteHTML. if ( $sel.type == 'Control' ) $sel.clear(); else if ( selection.getType() == CKEDITOR.SELECTION_TEXT ) { // Due to IE bugs on handling contenteditable=false blocks // (#6005), we need to make some checks and eventually // delete the selection first. range = selection.getRanges()[ 0 ]; var endContainer = range && range.endContainer; if ( endContainer && endContainer.type == CKEDITOR.NODE_ELEMENT && endContainer.getAttribute( 'contenteditable' ) == 'false' && range.checkBoundaryOfElement( endContainer, CKEDITOR.END ) ) { range.setEndAfter( range.endContainer ); range.deleteContents(); } } $sel.createRange().pasteHTML( data ); if ( selIsLocked ) this.getSelection().lock(); } else this.document.$.execCommand( 'inserthtml', false, data ); // Webkit does not scroll to the cursor position after pasting (#5558) if ( CKEDITOR.env.webkit ) { selection = this.getSelection(); selection.scrollIntoView(); } } function doInsertText( text ) { var selection = this.getSelection(), mode = selection.getStartElement().hasAscendant( 'pre', true ) ? CKEDITOR.ENTER_BR : this.config.enterMode, isEnterBrMode = mode == CKEDITOR.ENTER_BR; var html = CKEDITOR.tools.htmlEncode( text.replace( /\r\n|\r/g, '\n' ) ); // Convert leading and trailing whitespaces into   html = html.replace( /^[ \t]+|[ \t]+$/g, function( match, offset, s ) { if ( match.length == 1 ) // one space, preserve it return ' '; else if ( !offset ) // beginning of block return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' '; else // end of block return ' ' + CKEDITOR.tools.repeat( ' ', match.length - 1 ); } ); // Convert subsequent whitespaces into   html = html.replace( /[ \t]{2,}/g, function ( match ) { return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' '; } ); var paragraphTag = mode == CKEDITOR.ENTER_P ? 'p' : 'div'; // Two line-breaks create one paragraph. if ( !isEnterBrMode ) { html = html.replace( /(\n{2})([\s\S]*?)(?:$|\1)/g, function( match, group1, text ) { return '<'+paragraphTag + '>' + text + ''; }); } // One
per line-break. html = html.replace( /\n/g, '
' ); // Compensate padding
for non-IE. if ( !( isEnterBrMode || CKEDITOR.env.ie ) ) { html = html.replace( new RegExp( '
(?=)' ), function( match ) { return CKEDITOR.tools.repeat( match, 2 ); } ); } // Inline styles have to be inherited in Firefox. if ( CKEDITOR.env.gecko || CKEDITOR.env.webkit ) { var path = new CKEDITOR.dom.elementPath( selection.getStartElement() ), context = []; for ( var i = 0; i < path.elements.length; i++ ) { var tag = path.elements[ i ].getName(); if ( tag in CKEDITOR.dtd.$inline ) context.unshift( path.elements[ i ].getOuterHtml().match( /^<.*?>/) ); else if ( tag in CKEDITOR.dtd.$block ) break; } // Reproduce the context by preceding the pasted HTML with opening inline tags. html = context.join( '' ) + html; } doInsertHtml.call( this, html ); } function doInsertElement( element ) { var selection = this.getSelection(), ranges = selection.getRanges(), elementName = element.getName(), isBlock = CKEDITOR.dtd.$block[ elementName ]; var selIsLocked = selection.isLocked; if ( selIsLocked ) selection.unlock(); var range, clone, lastElement, bookmark; for ( var i = ranges.length - 1 ; i >= 0 ; i-- ) { range = ranges[ i ]; if ( !range.checkReadOnly() ) { // Remove the original contents, merge splitted nodes. range.deleteContents( 1 ); clone = !i && element || element.clone( 1 ); // If we're inserting a block at dtd-violated position, split // the parent blocks until we reach blockLimit. var current, dtd; if ( isBlock ) { while ( ( current = range.getCommonAncestor( 0, 1 ) ) && ( dtd = CKEDITOR.dtd[ current.getName() ] ) && !( dtd && dtd [ elementName ] ) ) { // Split up inline elements. if ( current.getName() in CKEDITOR.dtd.span ) range.splitElement( current ); // If we're in an empty block which indicate a new paragraph, // simply replace it with the inserting block.(#3664) else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { range.setStartBefore( current ); range.collapse( true ); current.remove(); } else range.splitBlock(); } } // Insert the new node. range.insertNode( clone ); // Save the last element reference so we can make the // selection later. if ( !lastElement ) lastElement = clone; } } if ( lastElement ) { range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END ); // If we're inserting a block element immediatelly followed by // another block element, the selection must move there. (#3100,#5436) if ( isBlock ) { var next = lastElement.getNext( notWhitespaceEval ), nextName = next && next.type == CKEDITOR.NODE_ELEMENT && next.getName(); // Check if it's a block element that accepts text. if ( nextName && CKEDITOR.dtd.$block[ nextName ] && CKEDITOR.dtd[ nextName ]['#'] ) range.moveToElementEditStart( next ); } } selection.selectRanges( [ range ] ); if ( selIsLocked ) this.getSelection().lock(); } // DOM modification here should not bother dirty flag.(#4385) function restoreDirty( editor ) { if ( !editor.checkDirty() ) setTimeout( function(){ editor.resetDirty(); }, 0 ); } var isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ), isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ); function isNotEmpty( node ) { return isNotWhitespace( node ) && isNotBookmark( node ); } function isNbsp( node ) { return node.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( node.getText() ).match( /^(?: |\xa0)$/ ); } function restoreSelection( selection ) { if ( selection.isLocked ) { selection.unlock(); setTimeout( function() { selection.lock(); }, 0 ); } } function isBlankParagraph( block ) { return block.getOuterHtml().match( emptyParagraphRegexp ); } isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ); // Gecko need a key event to 'wake up' the editing // ability when document is empty.(#3864, #5781) function activateEditing( editor ) { var win = editor.window, doc = editor.document, body = editor.document.getBody(), bodyFirstChild = body.getFirst(), bodyChildsNum = body.getChildren().count(); if ( !bodyChildsNum || bodyChildsNum == 1 && bodyFirstChild.type == CKEDITOR.NODE_ELEMENT && bodyFirstChild.hasAttribute( '_moz_editor_bogus_node' ) ) { restoreDirty( editor ); // Memorize scroll position to restore it later (#4472). var hostDocument = editor.element.getDocument(); var hostDocumentElement = hostDocument.getDocumentElement(); var scrollTop = hostDocumentElement.$.scrollTop; var scrollLeft = hostDocumentElement.$.scrollLeft; // Simulating keyboard character input by dispatching a keydown of white-space text. var keyEventSimulate = doc.$.createEvent( "KeyEvents" ); keyEventSimulate.initKeyEvent( 'keypress', true, true, win.$, false, false, false, false, 0, 32 ); doc.$.dispatchEvent( keyEventSimulate ); if ( scrollTop != hostDocumentElement.$.scrollTop || scrollLeft != hostDocumentElement.$.scrollLeft ) hostDocument.getWindow().$.scrollTo( scrollLeft, scrollTop ); // Restore the original document status by placing the cursor before a bogus br created (#5021). bodyChildsNum && body.getFirst().remove(); doc.getBody().appendBogus(); var nativeRange = new CKEDITOR.dom.range( doc ); nativeRange.setStartAt( body , CKEDITOR.POSITION_AFTER_START ); nativeRange.select(); } } /** * Auto-fixing block-less content by wrapping paragraph (#3190), prevent * non-exitable-block by padding extra br.(#3189) */ function onSelectionChangeFixBody( evt ) { var editor = evt.editor, path = evt.data.path, blockLimit = path.blockLimit, selection = evt.data.selection, range = selection.getRanges()[0], body = editor.document.getBody(), enterMode = editor.config.enterMode; if ( CKEDITOR.env.gecko ) { activateEditing( editor ); // Ensure bogus br could help to move cursor (out of styles) to the end of block. (#7041) var pathBlock = path.block || path.blockLimit, lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); // Check some specialities of the current path block: // 1. It is really displayed as block; (#7221) // 2. It doesn't end with one inner block; (#7467) // 3. It doesn't have bogus br yet. if ( pathBlock && pathBlock.isBlockBoundary() && !( lastNode && lastNode.type == CKEDITOR.NODE_ELEMENT && lastNode.isBlockBoundary() ) && !pathBlock.is( 'pre' ) && !pathBlock.getBogus() ) { pathBlock.appendBogus(); } } // When we're in block enter mode, a new paragraph will be established // to encapsulate inline contents right under body. (#3657) if ( editor.config.autoParagraph !== false && enterMode != CKEDITOR.ENTER_BR && range.collapsed && blockLimit.getName() == 'body' && !path.block ) { var fixedBlock = range.fixBlock( true, editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); // For IE, we should remove any filler node which was introduced before. if ( CKEDITOR.env.ie ) { var first = fixedBlock.getFirst( isNotEmpty ); first && isNbsp( first ) && first.remove(); } // If the fixed block is actually blank and is already followed by an exitable blank // block, we should revert the fix and move into the existed one. (#3684) if ( isBlankParagraph( fixedBlock ) ) { var element = fixedBlock.getNext( isNotWhitespace ); if ( element && element.type == CKEDITOR.NODE_ELEMENT && !nonEditable( element ) ) { range.moveToElementEditStart( element ); fixedBlock.remove(); } else { element = fixedBlock.getPrevious( isNotWhitespace ); if ( element && element.type == CKEDITOR.NODE_ELEMENT && !nonEditable( element ) ) { range.moveToElementEditEnd( element ); fixedBlock.remove(); } } } range.select(); // Cancel this selection change in favor of the next (correct). (#6811) evt.cancel(); } // Browsers are incapable of moving cursor out of certain block elements (e.g. table, div, pre) // at the end of document, makes it unable to continue adding content, we have to make this // easier by opening an new empty paragraph. var testRange = new CKEDITOR.dom.range( editor.document ); testRange.moveToElementEditEnd( editor.document.getBody() ); var testPath = new CKEDITOR.dom.elementPath( testRange.startContainer ); if ( !testPath.blockLimit.is( 'body') ) { var paddingBlock; if ( enterMode != CKEDITOR.ENTER_BR ) paddingBlock = body.append( editor.document.createElement( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ); else paddingBlock = body; if ( !CKEDITOR.env.ie ) paddingBlock.appendBogus(); } } CKEDITOR.plugins.add( 'wysiwygarea', { requires : [ 'editingblock' ], init : function( editor ) { var fixForBody = ( editor.config.enterMode != CKEDITOR.ENTER_BR && editor.config.autoParagraph !== false ) ? editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false; var frameLabel = editor.lang.editorTitle.replace( '%1', editor.name ); var contentDomReadyHandler; editor.on( 'editingBlockReady', function() { var mainElement, iframe, isLoadingData, isPendingFocus, frameLoaded, fireMode; // Support for custom document.domain in IE. var isCustomDomain = CKEDITOR.env.isCustomDomain(); // Creates the iframe that holds the editable document. var createIFrame = function( data ) { if ( iframe ) iframe.remove(); var src = 'document.open();' + // The document domain must be set any time we // call document.open(). ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) + 'document.close();'; // With IE, the custom domain has to be taken care at first, // for other browers, the 'src' attribute should be left empty to // trigger iframe's 'load' event. src = CKEDITOR.env.air ? 'javascript:void(0)' : CKEDITOR.env.ie ? 'javascript:void(function(){' + encodeURIComponent( src ) + '}())' : ''; iframe = CKEDITOR.dom.element.createFromHtml( '' ); // Running inside of Firefox chrome the load event doesn't bubble like in a normal page (#5689) if ( document.location.protocol == 'chrome:' ) CKEDITOR.event.useCapture = true; // With FF, it's better to load the data on iframe.load. (#3894,#4058) iframe.on( 'load', function( ev ) { frameLoaded = 1; ev.removeListener(); var doc = iframe.getFrameDocument(); doc.write( data ); CKEDITOR.env.air && contentDomReady( doc.getWindow().$ ); }); // Reset adjustment back to default (#5689) if ( document.location.protocol == 'chrome:' ) CKEDITOR.event.useCapture = false; mainElement.append( iframe ); }; // The script that launches the bootstrap logic on 'domReady', so the document // is fully editable even before the editing iframe is fully loaded (#4455). contentDomReadyHandler = CKEDITOR.tools.addFunction( contentDomReady ); var activationScript = ''; // Editing area bootstrap code. function contentDomReady( domWindow ) { if ( !frameLoaded ) return; frameLoaded = 0; editor.fire( 'ariaWidget', iframe ); var domDocument = domWindow.document, body = domDocument.body; // Remove this script from the DOM. var script = domDocument.getElementById( "cke_actscrpt" ); script && script.parentNode.removeChild( script ); body.spellcheck = !editor.config.disableNativeSpellChecker; var editable = !editor.readOnly; if ( CKEDITOR.env.ie ) { // Don't display the focus border. body.hideFocus = true; // Disable and re-enable the body to avoid IE from // taking the editing focus at startup. (#141 / #523) body.disabled = true; body.contentEditable = editable; body.removeAttribute( 'disabled' ); } else { // Avoid opening design mode in a frame window thread, // which will cause host page scrolling.(#4397) setTimeout( function() { // Prefer 'contentEditable' instead of 'designMode'. (#3593) if ( CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 || CKEDITOR.env.opera ) domDocument.$.body.contentEditable = editable; else if ( CKEDITOR.env.webkit ) domDocument.$.body.parentNode.contentEditable = editable; else domDocument.$.designMode = editable? 'off' : 'on'; }, 0 ); } editable && CKEDITOR.env.gecko && CKEDITOR.tools.setTimeout( activateEditing, 0, null, editor ); domWindow = editor.window = new CKEDITOR.dom.window( domWindow ); domDocument = editor.document = new CKEDITOR.dom.document( domDocument ); editable && domDocument.on( 'dblclick', function( evt ) { var element = evt.data.getTarget(), data = { element : element, dialog : '' }; editor.fire( 'doubleclick', data ); data.dialog && editor.openDialog( data.dialog ); }); // Prevent automatic submission in IE #6336 CKEDITOR.env.ie && domDocument.on( 'click', function( evt ) { var element = evt.data.getTarget(); if ( element.is( 'input' ) ) { var type = element.getAttribute( 'type' ); if ( type == 'submit' || type == 'reset' ) evt.data.preventDefault(); } }); // Gecko/Webkit need some help when selecting control type elements. (#3448) if ( !( CKEDITOR.env.ie || CKEDITOR.env.opera ) ) { domDocument.on( 'mousedown', function( ev ) { var control = ev.data.getTarget(); if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) ) editor.getSelection().selectElement( control ); } ); } if ( CKEDITOR.env.gecko ) { domDocument.on( 'mouseup', function( ev ) { if ( ev.data.$.button == 2 ) { var target = ev.data.getTarget(); // Prevent right click from selecting an empty block even // when selection is anchored inside it. (#5845) if ( !target.getOuterHtml().replace( emptyParagraphRegexp, '' ) ) { var range = new CKEDITOR.dom.range( domDocument ); range.moveToElementEditStart( target ); range.select( true ); } } } ); } // Prevent the browser opening links in read-only blocks. (#6032) domDocument.on( 'click', function( ev ) { ev = ev.data; if ( ev.getTarget().is( 'a' ) && ev.$.button != 2 ) ev.preventDefault(); }); // Webkit: avoid from editing form control elements content. if ( CKEDITOR.env.webkit ) { // Mark that cursor will right blinking (#7113). domDocument.on( 'mousedown', function() { wasFocused = 1; } ); // Prevent from tick checkbox/radiobox/select domDocument.on( 'click', function( ev ) { if ( ev.data.getTarget().is( 'input', 'select' ) ) ev.data.preventDefault(); } ); // Prevent from editig textfield/textarea value. domDocument.on( 'mouseup', function( ev ) { if ( ev.data.getTarget().is( 'input', 'textarea' ) ) ev.data.preventDefault(); } ); } // IE standard compliant in editing frame doesn't focus the editor when // clicking outside actual content, manually apply the focus. (#1659) if ( editable && CKEDITOR.env.ie && domDocument.$.compatMode == 'CSS1Compat' || CKEDITOR.env.gecko || CKEDITOR.env.opera ) { var htmlElement = domDocument.getDocumentElement(); htmlElement.on( 'mousedown', function( evt ) { // Setting focus directly on editor doesn't work, we // have to use here a temporary element to 'redirect' // the focus. if ( evt.data.getTarget().equals( htmlElement ) ) { if ( CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 ) blinkCursor(); focusGrabber.focus(); } } ); } var focusTarget = CKEDITOR.env.ie ? iframe : domWindow; focusTarget.on( 'blur', function() { editor.focusManager.blur(); }); var wasFocused; focusTarget.on( 'focus', function() { var doc = editor.document; if ( editable && CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 ) blinkCursor(); else if ( CKEDITOR.env.opera ) doc.getBody().focus(); // Webkit needs focus for the first time on the HTML element. (#6153) else if ( CKEDITOR.env.webkit ) { if ( !wasFocused ) { editor.document.getDocumentElement().focus(); wasFocused = 1; } } editor.focusManager.focus(); }); var keystrokeHandler = editor.keystrokeHandler; // Prevent backspace from navigating off the page. keystrokeHandler.blockedKeystrokes[ 8 ] = !editable; keystrokeHandler.attach( domDocument ); domDocument.getDocumentElement().addClass( domDocument.$.compatMode ); // Override keystroke behaviors. editable && domDocument.on( 'keydown', function( evt ) { var keyCode = evt.data.getKeystroke(); // Backspace OR Delete. if ( keyCode in { 8 : 1, 46 : 1 } ) { var sel = editor.getSelection(), selected = sel.getSelectedElement(), range = sel.getRanges()[ 0 ]; // Override keystrokes which should have deletion behavior // on fully selected element . (#4047) (#7645) if ( selected ) { // Make undo snapshot. editor.fire( 'saveSnapshot' ); // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will // break up the selection, safely manage it here. (#4795) range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); // Remove the control manually. selected.remove(); range.select(); editor.fire( 'saveSnapshot' ); evt.data.preventDefault(); return; } } } ); // PageUp/PageDown scrolling is broken in document // with standard doctype, manually fix it. (#4736) if ( CKEDITOR.env.ie && domDocument.$.compatMode == 'CSS1Compat' ) { var pageUpDownKeys = { 33 : 1, 34 : 1 }; domDocument.on( 'keydown', function( evt ) { if ( evt.data.getKeystroke() in pageUpDownKeys ) { setTimeout( function () { editor.getSelection().scrollIntoView(); }, 0 ); } } ); } // Prevent IE from leaving new paragraph after deleting all contents in body. (#6966) if ( CKEDITOR.env.ie && editor.config.enterMode != CKEDITOR.ENTER_P ) { domDocument.on( 'selectionchange', function() { var body = domDocument.getBody(), range = editor.getSelection().getRanges()[ 0 ]; if ( body.getHtml().match( /^

 <\/p>$/i ) && range.startContainer.equals( body ) ) { // Avoid the ambiguity from a real user cursor position. setTimeout( function () { range = editor.getSelection().getRanges()[ 0 ]; if ( !range.startContainer.equals ( 'body' ) ) { body.getFirst().remove( 1 ); range.moveToElementEditEnd( body ); range.select( 1 ); } }, 0 ); } }); } // Adds the document body as a context menu target. if ( editor.contextMenu ) editor.contextMenu.addTarget( domDocument, editor.config.browserContextMenuOnCtrl !== false ); setTimeout( function() { editor.fire( 'contentDom' ); if ( fireMode ) { editor.mode = 'wysiwyg'; editor.fire( 'mode', { previousMode : editor._.previousMode } ); fireMode = false; } isLoadingData = false; if ( isPendingFocus ) { editor.focus(); isPendingFocus = false; } setTimeout( function() { editor.fire( 'dataReady' ); }, 0 ); // IE, Opera and Safari may not support it and throw errors. try { editor.document.$.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ); } catch(e) {} if ( editor.config.disableObjectResizing ) { try { editor.document.$.execCommand( 'enableObjectResizing', false, false ); } catch(e) { // For browsers in which the above method failed, we can cancel the resizing on the fly (#4208) editor.document.getBody().on( CKEDITOR.env.ie ? 'resizestart' : 'resize', function( evt ) { evt.data.preventDefault(); }); } } /* * IE BUG: IE might have rendered the iframe with invisible contents. * (#3623). Push some inconsequential CSS style changes to force IE to * refresh it. * * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not * fix the problem. :( */ if ( CKEDITOR.env.ie ) { setTimeout( function() { if ( editor.document ) { var $body = editor.document.$.body; $body.runtimeStyle.marginBottom = '0px'; $body.runtimeStyle.marginBottom = ''; } }, 1000 ); } }, 0 ); } editor.addMode( 'wysiwyg', { load : function( holderElement, data, isSnapshot ) { mainElement = holderElement; if ( CKEDITOR.env.ie && CKEDITOR.env.quirks ) holderElement.setStyle( 'position', 'relative' ); // The editor data "may be dirty" after this // point. editor.mayBeDirty = true; fireMode = true; if ( isSnapshot ) this.loadSnapshotData( data ); else this.loadData( data ); }, loadData : function( data ) { isLoadingData = true; editor._.dataStore = { id : 1 }; var config = editor.config, fullPage = config.fullPage, docType = config.docType; // Build the additional stuff to be included into . var headExtra = ''; !fullPage && ( headExtra = CKEDITOR.tools.buildStyleHtml( editor.config.contentsCss ) + headExtra ); var baseTag = config.baseHref ? '' : ''; if ( fullPage ) { // Search and sweep out the doctype declaration. data = data.replace( /]*>/i, function( match ) { editor.docType = docType = match; return ''; }).replace( /<\?xml\s[^\?]*\?>/i, function( match ) { editor.xmlDeclaration = match; return ''; }); } // Get the HTML version of the data. if ( editor.dataProcessor ) data = editor.dataProcessor.toHtml( data, fixForBody ); if ( fullPage ) { // Check if the tag is available. if ( !(/]/).test( data ) ) data = '' + data; // Check if the tag is available. if ( !(/]/).test( data ) ) data = '' + data + ''; // Check if the tag is available. if ( !(/]/).test( data ) ) data = data.replace( /]*>/, '$&' ) ; else if ( !(/]/).test( data ) ) data = data.replace( /]*>/, '$&' ) ; // The base must be the first tag in the HEAD, e.g. to get relative // links on styles. baseTag && ( data = data.replace( //, '$&' + baseTag ) ); // Inject the extra stuff into . // Attention: do not change it before testing it well. (V2) // This is tricky... if the head ends with , // Firefox will break. But, it works if we place our extra stuff as // the last elements in the HEAD. data = data.replace( /<\/head\s*>/, headExtra + '$&' ); // Add the DOCTYPE back to it. data = docType + data; } else { data = config.docType + '' + '' + '' + frameLabel + '' + baseTag + headExtra + '' + '' + data + ''; } // Distinguish bogus to normal BR at the end of document for Mozilla. (#5293). if ( CKEDITOR.env.gecko ) data = data.replace( /
(?=\s*<\/(:?html|body)>)/, '$&
' ); data += activationScript; // The iframe is recreated on each call of setData, so we need to clear DOM objects this.onDispose(); createIFrame( data ); }, getData : function() { var config = editor.config, fullPage = config.fullPage, docType = fullPage && editor.docType, xmlDeclaration = fullPage && editor.xmlDeclaration, doc = iframe.getFrameDocument(); var data = fullPage ? doc.getDocumentElement().getOuterHtml() : doc.getBody().getHtml(); // BR at the end of document is bogus node for Mozilla. (#5293). if ( CKEDITOR.env.gecko ) data = data.replace( /
(?=\s*(:?$|<\/body>))/, '' ); if ( editor.dataProcessor ) data = editor.dataProcessor.toDataFormat( data, fixForBody ); // Reset empty if the document contains only one empty paragraph. if ( config.ignoreEmptyParagraph ) data = data.replace( emptyParagraphRegexp, function( match, lookback ) { return lookback; } ); if ( xmlDeclaration ) data = xmlDeclaration + '\n' + data; if ( docType ) data = docType + '\n' + data; return data; }, getSnapshotData : function() { return iframe.getFrameDocument().getBody().getHtml(); }, loadSnapshotData : function( data ) { iframe.getFrameDocument().getBody().setHtml( data ); }, onDispose : function() { if ( !editor.document ) return; editor.document.getDocumentElement().clearCustomData(); editor.document.getBody().clearCustomData(); editor.window.clearCustomData(); editor.document.clearCustomData(); iframe.clearCustomData(); /* * IE BUG: When destroying editor DOM with the selection remains inside * editing area would break IE7/8's selection system, we have to put the editing * iframe offline first. (#3812 and #5441) */ iframe.remove(); }, unload : function( holderElement ) { this.onDispose(); editor.window = editor.document = iframe = mainElement = isPendingFocus = null; editor.fire( 'contentDomUnload' ); }, focus : function() { var win = editor.window; if ( isLoadingData ) isPendingFocus = true; else if ( win ) { // AIR needs a while to focus when moving from a link. CKEDITOR.env.air ? setTimeout( function () { win.focus(); }, 0 ) : win.focus(); editor.selectionChange(); } } }); editor.on( 'insertHtml', onInsert( doInsertHtml ) , null, null, 20 ); editor.on( 'insertElement', onInsert( doInsertElement ), null, null, 20 ); editor.on( 'insertText', onInsert( doInsertText ), null, null, 20 ); // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189) editor.on( 'selectionChange', function( evt ) { if ( editor.readOnly ) return; var sel = editor.getSelection(); // Do it only when selection is not locked. (#8222) if ( sel && !sel.isLocked ) { var isDirty = editor.checkDirty(); editor.fire( 'saveSnapshot', { contentOnly : 1 } ); onSelectionChangeFixBody.call( this, evt ); editor.fire( 'updateSnapshot' ); !isDirty && editor.resetDirty(); } }, null, null, 1 ); }); var titleBackup; // Setting voice label as window title, backup the original one // and restore it before running into use. editor.on( 'contentDom', function() { var title = editor.document.getElementsByTag( 'title' ).getItem( 0 ); title.data( 'cke-title', editor.document.$.title ); editor.document.$.title = frameLabel; }); editor.on( 'readOnly', function() { if ( editor.mode == 'wysiwyg' ) { // Symply reload the wysiwyg area. It'll take care of read-only. var wysiwyg = editor.getMode(); wysiwyg.loadData( wysiwyg.getData() ); } }); // IE>=8 stricts mode doesn't have 'contentEditable' in effect // on element unless it has layout. (#5562) if ( CKEDITOR.document.$.documentMode >= 8 ) { editor.addCss( 'html.CSS1Compat [contenteditable=false]{ min-height:0 !important;}' ); var selectors = []; for ( var tag in CKEDITOR.dtd.$removeEmpty ) selectors.push( 'html.CSS1Compat ' + tag + '[contenteditable=false]' ); editor.addCss( selectors.join( ',' ) + '{ display:inline-block;}' ); } // Set the HTML style to 100% to have the text cursor in affect (#6341) else if ( CKEDITOR.env.gecko ) { editor.addCss( 'html { height: 100% !important; }' ); editor.addCss( 'img:-moz-broken { -moz-force-broken-image-icon : 1; width : 24px; height : 24px; }' ); } /* #3658: [IE6] Editor document has horizontal scrollbar on long lines To prevent this misbehavior, we show the scrollbar always */ /* #6341: The text cursor must be set on the editor area. */ /* #6632: Avoid having "text" shape of cursor in IE7 scrollbars.*/ editor.addCss( 'html { _overflow-y: scroll; cursor: text; *cursor:auto;}' ); // Use correct cursor for these elements editor.addCss( 'img, input, textarea { cursor: default;}' ); // Switch on design mode for a short while and close it after then. function blinkCursor( retry ) { if ( editor.readOnly ) return; CKEDITOR.tools.tryThese( function() { editor.document.$.designMode = 'on'; setTimeout( function() { editor.document.$.designMode = 'off'; if ( CKEDITOR.currentInstance == editor ) editor.document.getBody().focus(); }, 50 ); }, function() { // The above call is known to fail when parent DOM // tree layout changes may break design mode. (#5782) // Refresh the 'contentEditable' is a cue to this. editor.document.$.designMode = 'off'; var body = editor.document.getBody(); body.setAttribute( 'contentEditable', false ); body.setAttribute( 'contentEditable', true ); // Try it again once.. !retry && blinkCursor( 1 ); }); } // Create an invisible element to grab focus. if ( CKEDITOR.env.gecko || CKEDITOR.env.ie || CKEDITOR.env.opera ) { var focusGrabber; editor.on( 'uiReady', function() { focusGrabber = editor.container.append( CKEDITOR.dom.element.createFromHtml( // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049) '' ) ); focusGrabber.on( 'focus', function() { editor.focus(); } ); editor.focusGrabber = focusGrabber; } ); editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( contentDomReadyHandler ); focusGrabber.clearCustomData(); delete editor.focusGrabber; } ); } // Disable form elements editing mode provided by some browers. (#5746) editor.on( 'insertElement', function ( evt ) { var element = evt.data; if ( element.type == CKEDITOR.NODE_ELEMENT && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) { // We should flag that the element was locked by our code so // it'll be editable by the editor functions (#6046). var readonly = element.getAttribute( 'contenteditable' ) == 'false'; if ( !readonly ) { element.data( 'cke-editable', element.hasAttribute( 'contenteditable' ) ? 'true' : '1' ); element.setAttribute( 'contenteditable', false ); } } }); } }); // Fixing Firefox 'Back-Forward Cache' break design mode. (#4514) if ( CKEDITOR.env.gecko ) { (function() { var body = document.body; if ( !body ) window.addEventListener( 'load', arguments.callee, false ); else { var currentHandler = body.getAttribute( 'onpageshow' ); body.setAttribute( 'onpageshow', ( currentHandler ? currentHandler + ';' : '') + 'event.persisted && (function(){' + 'var allInstances = CKEDITOR.instances, editor, doc;' + 'for ( var i in allInstances )' + '{' + ' editor = allInstances[ i ];' + ' doc = editor.document;' + ' if ( doc )' + ' {' + ' doc.$.designMode = "off";' + ' doc.$.designMode = "on";' + ' }' + '}' + '})();' ); } } )(); } })(); /** * Disables the ability of resize objects (image and tables) in the editing * area. * @type Boolean * @default false * @example * config.disableObjectResizing = true; */ CKEDITOR.config.disableObjectResizing = false; /** * Disables the "table tools" offered natively by the browser (currently * Firefox only) to make quick table editing operations, like adding or * deleting rows and columns. * @type Boolean * @default true * @example * config.disableNativeTableHandles = false; */ CKEDITOR.config.disableNativeTableHandles = true; /** * Disables the built-in words spell checker if browser provides one.

* * Note: Although word suggestions provided by browsers (natively) will not appear in CKEditor's default context menu, * users can always reach the native context menu by holding the Ctrl key when right-clicking if {@link CKEDITOR.config.browserContextMenuOnCtrl} * is enabled or you're simply not using the context menu plugin. * * @type Boolean * @default true * @example * config.disableNativeSpellChecker = false; */ CKEDITOR.config.disableNativeSpellChecker = true; /** * Whether the editor must output an empty value ("") if it's contents is made * by an empty paragraph only. * @type Boolean * @default true * @example * config.ignoreEmptyParagraph = false; */ CKEDITOR.config.ignoreEmptyParagraph = true; /** * Fired when data is loaded and ready for retrieval in an editor instance. * @name CKEDITOR.editor#dataReady * @event */ /** * Whether automatically create wrapping blocks around inline contents inside document body, * this helps to ensure the integrality of the block enter mode. * Note: Changing the default value might introduce unpredictable usability issues. * @name CKEDITOR.config.autoParagraph * @since 3.6 * @type Boolean * @default true * @example * config.autoParagraph = false; */ /** * Fired when some elements are added to the document * @name CKEDITOR.editor#ariaWidget * @event * @param {Object} element The element being added */