/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
'use strict';
( function() {
var template = ' │
// │ │ │ ',
templateBlock = new CKEDITOR.template(
'
│
// │ │
│ │
// │ │
│
// │ │
│ │
// │ │
│
// │ │
│
│ // │ │
│ │
// │ │
│
remains for styling purposes. // // 3.
.
if ( !( el.name in { div: 1, p: 1 } ) )
return false;
var children = el.children;
// Centering wrapper can have only one child.
if ( children.length !== 1 )
return false;
var child = children[ 0 ];
// Only , only centering wrapper.
div: {
match: centerWrapperChecker( editor )
},
p: {
match: centerWrapperChecker( editor )
},
img: {
attributes: '!src,alt,width,height'
},
figure: {
classes: '!' + editor.config.image2_captionedClass
},
figcaption: true
};
if ( alignClasses ) {
// Centering class from the config.
rules.div.classes = alignClasses[ 1 ];
rules.p.classes = rules.div.classes;
// Left/right classes from the config.
rules.img.classes = alignClasses[ 0 ] + ',' + alignClasses[ 2 ];
rules.figure.classes += ',' + rules.img.classes;
} else {
// Centering with text-align.
rules.div.styles = 'text-align';
rules.p.styles = 'text-align';
rules.img.styles = 'float';
rules.figure.styles = 'float,display';
}
return rules;
}
// Returns a set of widget feature rules, depending
// on editor configuration. Note that the following may not cover
// all the possible cases since requiredContent supports a single
// tag only.
//
// @param {CKEDITOR.editor}
// @returns {Object}
function getWidgetFeatures( editor ) {
var alignClasses = editor.config.image2_alignClasses,
features = {
dimension: {
requiredContent: 'img[width,height]'
},
align: {
requiredContent: 'img' +
( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float}' )
},
caption: {
requiredContent: 'figcaption'
}
};
return features;
}
// Returns element which is styled, considering current
// state of the widget.
//
// @see CKEDITOR.plugins.widget#applyStyle
// @param {CKEDITOR.plugins.widget} widget
// @returns {CKEDITOR.dom.element}
function getStyleableElement( widget ) {
return widget.data.hasCaption ? widget.element : widget.parts.image;
}
} )();
/**
* A CSS class applied to the ` can be first (only) child of centering wrapper,
// regardless of its type.
if ( !( child.name in validChildren ) )
return false;
// If centering wrapper is
can be the child.
//
or
or
only when enterMode
// is ENTER_(BR|DIV).
//
or
.
if ( !isLinkedOrStandaloneImage( child ) )
return false;
}
}
// Centering wrapper got to be... centering. If image2_alignClasses are defined,
// check for centering class. Otherwise, check the style.
if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
return true;
return false;
};
}
// Checks whether element is
or
.
//
// @param {CKEDITOR.htmlParser.element}
function isLinkedOrStandaloneImage( el ) {
if ( el.name == 'img' )
return true;
else if ( el.name == 'a' )
return el.children.length == 1 && el.getFirst( 'img' );
return false;
}
// Sets width and height of the widget image according to current widget data.
//
// @param {CKEDITOR.plugins.widget} widget
function setDimensions( widget ) {
var data = widget.data,
dimensions = { width: data.width, height: data.height },
image = widget.parts.image;
for ( var d in dimensions ) {
if ( dimensions[ d ] )
image.setAttribute( d, dimensions[ d ] );
else
image.removeAttribute( d );
}
}
// Defines all features related to drag-driven image resizing.
//
// @param {CKEDITOR.plugins.widget} widget
function setupResizer( widget ) {
var editor = widget.editor,
editable = editor.editable(),
doc = editor.document,
// Store the resizer in a widget for testing (https://dev.ckeditor.com/ticket/11004).
resizer = widget.resizer = doc.createElement( 'span' );
resizer.addClass( 'cke_image_resizer' );
resizer.setAttribute( 'title', editor.lang.image2.resizer );
resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
// Inline widgets don't need a resizer wrapper as an image spans the entire widget.
if ( !widget.inline ) {
var imageOrLink = widget.parts.link || widget.parts.image,
oldResizeWrapper = imageOrLink.getParent(),
resizeWrapper = doc.createElement( 'span' );
resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
resizeWrapper.append( imageOrLink );
resizeWrapper.append( resizer );
widget.element.append( resizeWrapper, true );
// Remove the old wrapper which could came from e.g. pasted HTML
// and which could be corrupted (e.g. resizer span has been lost).
if ( oldResizeWrapper.is( 'span' ) )
oldResizeWrapper.remove();
} else {
widget.wrapper.append( resizer );
}
// Calculate values of size variables and mouse offsets.
resizer.on( 'mousedown', function( evt ) {
var image = widget.parts.image,
// Don't update attributes if less than 15.
// This is to prevent images to visually disappear.
min = {
width: 15,
height: 15
},
max = getMaxSize(),
// "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
// subtract the difference to get proper width, etc. Without "factor",
// resizer starts working the opposite way.
factor = widget.data.align == 'right' ? -1 : 1,
// The x-coordinate of the mouse relative to the screen
// when button gets pressed.
startX = evt.data.$.screenX,
startY = evt.data.$.screenY,
// The initial dimensions and aspect ratio of the image.
startWidth = image.$.clientWidth,
startHeight = image.$.clientHeight,
ratio = startWidth / startHeight,
listeners = [],
// A class applied to editable during resizing.
cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
nativeEvt, newWidth, newHeight, updateData,
moveDiffX, moveDiffY, moveRatio;
// Save the undo snapshot first: before resizing.
editor.fire( 'saveSnapshot' );
// Mousemove listeners are removed on mouseup.
attachToDocuments( 'mousemove', onMouseMove, listeners );
// Clean up the mousemove listener. Update widget data if valid.
attachToDocuments( 'mouseup', onMouseUp, listeners );
// The entire editable will have the special cursor while resizing goes on.
editable.addClass( cursorClass );
// This is to always keep the resizer element visible while resizing.
resizer.addClass( 'cke_image_resizing' );
// Attaches an event to a global document if inline editor.
// Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
function attachToDocuments( name, callback, collection ) {
var globalDoc = CKEDITOR.document,
listeners = [];
if ( !doc.equals( globalDoc ) )
listeners.push( globalDoc.on( name, callback ) );
listeners.push( doc.on( name, callback ) );
if ( collection ) {
for ( var i = listeners.length; i--; )
collection.push( listeners.pop() );
}
}
// Calculate with first, and then adjust height, preserving ratio.
function adjustToX() {
newWidth = startWidth + factor * moveDiffX;
newHeight = Math.round( newWidth / ratio );
}
// Calculate height first, and then adjust width, preserving ratio.
function adjustToY() {
newHeight = startHeight - moveDiffY;
newWidth = Math.round( newHeight * ratio );
}
// This is how variables refer to the geometry.
// Note: x corresponds to moveOffset, this is the position of mouse
// Note: o corresponds to [startX, startY].
//
// +--------------+--------------+
// | | |
// | I | II |
// | | |
// +------------- o -------------+ _ _ _
// | | | ^
// | VI | III | | moveDiffY
// | | x _ _ _ _ _ v
// +--------------+---------|----+
// | |
// <------->
// moveDiffX
function onMouseMove( evt ) {
nativeEvt = evt.data.$;
// This is how far the mouse is from the point the button was pressed.
moveDiffX = nativeEvt.screenX - startX;
moveDiffY = startY - nativeEvt.screenY;
// This is the aspect ratio of the move difference.
moveRatio = Math.abs( moveDiffX / moveDiffY );
// Left, center or none-aligned widget.
if ( factor == 1 ) {
if ( moveDiffX <= 0 ) {
// Case: IV.
if ( moveDiffY <= 0 )
adjustToX();
// Case: I.
else {
if ( moveRatio >= ratio )
adjustToX();
else
adjustToY();
}
} else {
// Case: III.
if ( moveDiffY <= 0 ) {
if ( moveRatio >= ratio )
adjustToY();
else
adjustToX();
}
// Case: II.
else {
adjustToY();
}
}
}
// Right-aligned widget. It mirrors behaviours, so I becomes II,
// IV becomes III and vice-versa.
else {
if ( moveDiffX <= 0 ) {
// Case: IV.
if ( moveDiffY <= 0 ) {
if ( moveRatio >= ratio )
adjustToY();
else
adjustToX();
}
// Case: I.
else {
adjustToY();
}
} else {
// Case: III.
if ( moveDiffY <= 0 )
adjustToX();
// Case: II.
else {
if ( moveRatio >= ratio ) {
adjustToX();
} else {
adjustToY();
}
}
}
}
if ( isAllowedSize( newWidth, newHeight ) ) {
updateData = { width: newWidth, height: newHeight };
image.setAttributes( updateData );
}
}
function onMouseUp() {
var l;
while ( ( l = listeners.pop() ) )
l.removeListener();
// Restore default cursor by removing special class.
editable.removeClass( cursorClass );
// This is to bring back the regular behaviour of the resizer.
resizer.removeClass( 'cke_image_resizing' );
if ( updateData ) {
widget.setData( updateData );
// Save another undo snapshot: after resizing.
editor.fire( 'saveSnapshot' );
}
// Don't update data twice or more.
updateData = false;
}
function getMaxSize() {
var maxSize = editor.config.image2_maxSize,
natural;
if ( !maxSize ) {
return null;
}
maxSize = CKEDITOR.tools.copy( maxSize );
natural = CKEDITOR.plugins.image2.getNatural( image );
maxSize.width = Math.max( maxSize.width === 'natural' ? natural.width : maxSize.width, min.width );
maxSize.height = Math.max( maxSize.height === 'natural' ? natural.height : maxSize.height, min.width );
return maxSize;
}
function isAllowedSize( width, height ) {
var isTooSmall = width < min.width || height < min.height,
isTooBig = max && ( width > max.width || height > max.height );
return !isTooSmall && !isTooBig;
}
} );
// Change the position of the widget resizer when data changes.
widget.on( 'data', function() {
resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
} );
}
// Integrates widget alignment setting with justify
// plugin's commands (execution and refreshment).
// @param {CKEDITOR.editor} editor
// @param {String} value 'left', 'right', 'center' or 'block'
function alignCommandIntegrator( editor ) {
var execCallbacks = [],
enabled;
return function( value ) {
var command = editor.getCommand( 'justify' + value );
// Most likely, the justify plugin isn't loaded.
if ( !command )
return;
// This command will be manually refreshed along with
// other commands after exec.
execCallbacks.push( function() {
command.refresh( editor, editor.elementPath() );
} );
if ( value in { right: 1, left: 1, center: 1 } ) {
command.on( 'exec', function( evt ) {
var widget = getFocusedWidget( editor );
if ( widget ) {
widget.setData( 'align', value );
// Once the widget changed its align, all the align commands
// must be refreshed: the event is to be cancelled.
for ( var i = execCallbacks.length; i--; )
execCallbacks[ i ]();
evt.cancel();
}
} );
}
command.on( 'refresh', function( evt ) {
var widget = getFocusedWidget( editor ),
allowed = { right: 1, left: 1, center: 1 };
if ( !widget )
return;
// Cache "enabled" on first use. This is because filter#checkFeature may
// not be available during plugin's afterInit in the future — a moment when
// alignCommandIntegrator is called.
if ( enabled === undefined )
enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
// Don't allow justify commands when widget alignment is disabled (https://dev.ckeditor.com/ticket/11004).
if ( !enabled )
this.setState( CKEDITOR.TRISTATE_DISABLED );
else {
this.setState(
( widget.data.align == value ) ? (
CKEDITOR.TRISTATE_ON
) : (
( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
)
);
}
evt.cancel();
} );
};
}
function linkCommandIntegrator( editor ) {
// Nothing to integrate with if link is not loaded.
if ( !editor.plugins.link )
return;
var listener = CKEDITOR.on( 'dialogDefinition', function( evt ) {
var dialog = evt.data;
if ( dialog.name == 'link' ) {
var def = dialog.definition;
var onShow = def.onShow,
onOk = def.onOk;
def.onShow = function() {
var widget = getFocusedWidget( editor ),
displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
// Widget cannot be enclosed in a link, i.e.
// foo
*
* instead of:
*
*
*
* **Note**: Once this configuration option is set, corresponding style definitions
* must be supplied to the editor:
*
* * For {@glink guide/dev_framed classic editor} it can be done by defining additional
* styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
* styles must be provided on the target page where the content will be loaded.
* * For {@glink guide/dev_inline inline editor} the styles can be defined directly
* with `