/** * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ /** * @fileOverview The WYSIWYG Area plugin. It registers the "wysiwyg" editing * mode, which handles the main editing area space. */ ( function() { CKEDITOR.plugins.add( 'wysiwygarea', { init: function( editor ) { if ( editor.config.fullPage ) { editor.addFeature( { allowedContent: 'html head title; style [media,type]; body (*)[id]; meta link [*]', requiredContent: 'body' } ); } editor.addMode( 'wysiwyg', function( callback ) { var src = 'document.open();' + // In IE, the document domain must be set any time we call document.open(). ( CKEDITOR.env.ie ? '(' + CKEDITOR.tools.fixDomain + ')();' : '' ) + '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. // Microsoft Edge throws "Permission Denied" if treated like an IE (#13441). if ( CKEDITOR.env.air ) { src = 'javascript:void(0)'; // jshint ignore:line } else if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { src = 'javascript:void(function(){' + encodeURIComponent( src ) + '}())'; // jshint ignore:line } else { src = ''; } var iframe = CKEDITOR.dom.element.createFromHtml( '' ); iframe.setStyles( { width: '100%', height: '100%' } ); iframe.addClass( 'cke_wysiwyg_frame' ).addClass( 'cke_reset' ); var contentSpace = editor.ui.space( 'contents' ); contentSpace.append( iframe ); // Asynchronous iframe loading is only required in IE>8 and Gecko (other reasons probably). // Do not use it on WebKit as it'll break the browser-back navigation. var useOnloadEvent = ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) || CKEDITOR.env.gecko; if ( useOnloadEvent ) iframe.on( 'load', onLoad ); var frameLabel = editor.title, helpLabel = editor.fire( 'ariaEditorHelpLabel', {} ).label; if ( frameLabel ) { if ( CKEDITOR.env.ie && helpLabel ) frameLabel += ', ' + helpLabel; iframe.setAttribute( 'title', frameLabel ); } if ( helpLabel ) { var labelId = CKEDITOR.tools.getNextId(), desc = CKEDITOR.dom.element.createFromHtml( '' + helpLabel + '' ); contentSpace.append( desc, 1 ); iframe.setAttribute( 'aria-describedby', labelId ); } // Remove the ARIA description. editor.on( 'beforeModeUnload', function( evt ) { evt.removeListener(); if ( desc ) desc.remove(); } ); iframe.setAttributes( { tabIndex: editor.tabIndex, allowTransparency: 'true' } ); // Execute onLoad manually for all non IE||Gecko browsers. !useOnloadEvent && onLoad(); editor.fire( 'ariaWidget', iframe ); function onLoad( evt ) { evt && evt.removeListener(); editor.editable( new framedWysiwyg( editor, iframe.$.contentWindow.document.body ) ); editor.setData( editor.getData( 1 ), callback ); } } ); } } ); /** * Adds the path to a stylesheet file to the exisiting {@link CKEDITOR.config#contentsCss} value. * * **Note:** This method is available only with the `wysiwygarea` plugin and only affects * classic editors based on it (so it does not affect inline editors). * * editor.addContentsCss( 'assets/contents.css' ); * * @since 4.4 * @param {String} cssPath The path to the stylesheet file which should be added. * @member CKEDITOR.editor */ CKEDITOR.editor.prototype.addContentsCss = function( cssPath ) { var cfg = this.config, curContentsCss = cfg.contentsCss; // Convert current value into array. if ( !CKEDITOR.tools.isArray( curContentsCss ) ) cfg.contentsCss = curContentsCss ? [ curContentsCss ] : []; cfg.contentsCss.push( cssPath ); }; function onDomReady( win ) { var editor = this.editor, doc = win.document, body = doc.body; // Remove helper scripts from the DOM. var script = doc.getElementById( 'cke_actscrpt' ); script && script.parentNode.removeChild( script ); script = doc.getElementById( 'cke_shimscrpt' ); script && script.parentNode.removeChild( script ); script = doc.getElementById( 'cke_basetagscrpt' ); script && script.parentNode.removeChild( script ); body.contentEditable = true; 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.removeAttribute( 'disabled' ); } delete this._.isLoadingData; // Play the magic to alter element reference to the reloaded one. this.$ = body; doc = new CKEDITOR.dom.document( doc ); this.setup(); this.fixInitialSelection(); if ( CKEDITOR.env.ie ) { doc.getDocumentElement().addClass( doc.$.compatMode ); // Prevent IE from leaving new paragraph after deleting all contents in body. (#6966) editor.config.enterMode != CKEDITOR.ENTER_P && this.attachListener( doc, 'selectionchange', function() { var body = doc.getBody(), sel = editor.getSelection(), range = sel && sel.getRanges()[ 0 ]; if ( range && 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();
}
}, 0 );
}
} );
}
// Fix problem with cursor not appearing in Webkit and IE11+ when clicking below the body (#10945, #10906).
// Fix for older IEs (8-10 and QM) is placed inside selection.js.
if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version > 10 ) ) {
doc.getDocumentElement().on( 'mousedown', function( evt ) {
if ( evt.data.getTarget().is( 'html' ) ) {
// IE needs this timeout. Webkit does not, but it does not cause problems too.
setTimeout( function() {
editor.editable().focus();
} );
}
} );
}
// Config props: disableObjectResizing and disableNativeTableHandles handler.
objectResizeDisabler( editor );
// Enable dragging of position:absolute elements in IE.
try {
editor.document.$.execCommand( '2D-position', false, true );
} catch ( e ) {}
if ( CKEDITOR.env.gecko || CKEDITOR.env.ie && editor.document.$.compatMode == 'CSS1Compat' ) {
this.attachListener( this, 'keydown', function( evt ) {
var keyCode = evt.data.getKeystroke();
// PageUp OR PageDown
if ( keyCode == 33 || keyCode == 34 ) {
// PageUp/PageDown scrolling is broken in document
// with standard doctype, manually fix it. (#4736)
if ( CKEDITOR.env.ie ) {
setTimeout( function() {
editor.getSelection().scrollIntoView();
}, 0 );
}
// Page up/down cause editor selection to leak
// outside of editable thus we try to intercept
// the behavior, while it affects only happen
// when editor contents are not overflowed. (#7955)
else if ( editor.window.$.innerHeight > this.$.offsetHeight ) {
var range = editor.createRange();
range[ keyCode == 33 ? 'moveToElementEditStart' : 'moveToElementEditEnd' ]( this );
range.select();
evt.data.preventDefault();
}
}
} );
}
if ( CKEDITOR.env.ie ) {
// [IE] Iframe will still keep the selection when blurred, if
// focus is moved onto a non-editing host, e.g. link or button, but
// it becomes a problem for the object type selection, since the resizer
// handler attached on it will mark other part of the UI, especially
// for the dialog. (#8157)
// [IE<8 & Opera] Even worse For old IEs, the cursor will not vanish even if
// the selection has been moved to another text input in some cases. (#4716)
//
// Now the range restore is disabled, so we simply force IE to clean
// up the selection before blur.
this.attachListener( doc, 'blur', function() {
// Error proof when the editor is not visible. (#6375)
try {
doc.$.selection.empty();
} catch ( er ) {}
} );
}
if ( CKEDITOR.env.iOS ) {
// [iOS] If touch is bound to any parent of the iframe blur happens on any touch
// event and body becomes the focused element (#10714).
this.attachListener( doc, 'touchend', function() {
win.focus();
} );
}
var title = editor.document.getElementsByTag( 'title' ).getItem( 0 );
// document.title is malfunctioning on Chrome, so get value from the element (#12402).
title.data( 'cke-title', title.getText() );
// [IE] JAWS will not recognize the aria label we used on the iframe
// unless the frame window title string is used as the voice label,
// backup the original one and restore it on output.
if ( CKEDITOR.env.ie )
editor.document.$.title = this._.docTitle;
CKEDITOR.tools.setTimeout( function() {
// Editable is ready after first setData.
if ( this.status == 'unloaded' )
this.status = 'ready';
editor.fire( 'contentDom' );
if ( this._.isPendingFocus ) {
editor.focus();
this._.isPendingFocus = false;
}
setTimeout( function() {
editor.fire( 'dataReady' );
}, 0 );
}, 0, this );
}
var framedWysiwyg = CKEDITOR.tools.createClass( {
$: function() {
this.base.apply( this, arguments );
this._.frameLoadedHandler = CKEDITOR.tools.addFunction( function( win ) {
// Avoid opening design mode in a frame window thread,
// which will cause host page scrolling.(#4397)
CKEDITOR.tools.setTimeout( onDomReady, 0, this, win );
}, this );
this._.docTitle = this.getWindow().getFrame().getAttribute( 'title' );
},
base: CKEDITOR.editable,
proto: {
setData: function( data, isSnapshot ) {
var editor = this.editor;
if ( isSnapshot ) {
this.setHtml( data );
this.fixInitialSelection();
// Fire dataReady for the consistency with inline editors
// and because it makes sense. (#10370)
editor.fire( 'dataReady' );
}
else {
this._.isLoadingData = true;
editor._.dataStore = { id: 1 };
var config = editor.config,
fullPage = config.fullPage,
docType = config.docType;
// Build the additional stuff to be included into