silverstripe-framework/thirdparty/tinymce/plugins/advimagescale/editor_plugin_src.js

454 lines
19 KiB
JavaScript

/**
* TinyMCE Advanced Image Resize Helper Plugin
*
* Forces images to maintain aspect ratio while scaling - also optionally enforces
* min/max image dimensions, and appends width/height to the image URL for server-side
* resizing
*
* @author Marc Hodgins (modified to remove global variables: http://sourceforge.net/p/tinymce/plugins/186/)
* @link http://www.hodginsmedia.com Hodgins Media Ventures Inc.
* @copyright Copyright (C) 2008-2010 Hodgins Media Ventures Inc., All right reserved.
* @license http://www.opensource.org/licenses/lgpl-3.0.html LGPLv3
*/
(function() {
tinymce.create('tinymce.plugins.AdvImageScale', {
/**
* Initializes the plugin, this will be executed after the plugin has been created.
*
* @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
* @param {string} url Absolute URL to where the plugin is located.
*/
init : function(ed, url) {
/**
* Stores pre-resize image dimensions
* @var {array} (w,h)
*/
ed.originalDimensions = new Array();
/**
* Stores last dimensions before a resize
* @var {array} (w,h)
*/
ed.lastDimensions = new Array();
/**
* Track mousedown status in editor
* @var {boolean}
*/
ed.edMouseDown = false;
// Watch for mousedown (as a fall through to ensure that prepareImage() definitely
// got called on an image tag before mouseup).
//
// Normally this should have happened via the onPreProcess/onSetContent listeners, but
// for completeness we check once more here in case there are edge cases we've missed.
ed.onMouseDown.add(function(ed, e) {
var el = tinyMCE.activeEditor.selection.getNode();
if (el != null && el.nodeName == 'IMG') {
// prepare image for resizing
prepareImage(ed, e.target);
}
return true;
});
// Watch for mouseup (catch image resizes)
ed.onMouseUp.add(function(ed, e) {
var el = tinyMCE.activeEditor.selection.getNode();
if (el != null && el.nodeName == 'IMG') {
// setTimeout is necessary to allow the browser to complete the resize so we have new dimensions
setTimeout(function() {
constrainSize(ed, el);
}, 100);
}
return true;
});
/*****************************************************
* ENFORCE CONSTRAINTS ON CONTENT INSERTED INTO EDITOR
*****************************************************/
// Catch editor.setContent() events via onPreProcess (because onPreProcess allows us to
// modify the DOM before it is inserted, unlike onSetContent)
ed.onPreProcess.add(function(ed, o) {
if (!o.set) return; // only 'set' operations let us modify the nodes
// loop in each img node and run constrainSize
tinymce.each(ed.dom.select('img', o.node), function(currentNode) {
constrainSize(ed, currentNode);
});
});
// To be complete, we also need to watch for setContent() calls on the selection object so that
// constraints are enforced (i.e. in case an <img> tag is inserted via mceInsertContent).
// So, catch all insertions using the editor's selection object
ed.onInit.add(function(ed) {
// http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.dom.Selection/onSetContent
ed.selection.onSetContent.add(function(se, o) {
// @todo This seems to grab the entire editor contents - it works but could
// perform poorly on large documents
var currentNode = se.getNode();
tinymce.each(ed.dom.select('img', currentNode), function (currentNode) {
// IF condition required as tinyMCE inserts 24x24 placeholders uner some conditions
if (currentNode.id != "__mce_tmp")
constrainSize(ed, currentNode);
});
});
});
/*****************************
* DISALLOW EXTERNAL IMAGE DRAG/DROPS
*****************************/
// This is a hack. Listening for drag events wasn't working.
//
// Watches for mousedown and mouseup/dragdrop events within the editor. If a mouseup or
// dragdrop occurs in the editor without a preceeding mousedown, we assume it is an external
// dragdrop that should be rejected.
if (ed.getParam('advimagescale_reject_external_dragdrop', true)) {
// catch mousedowns mouseups and dragdrops (which are basically mouseups too..)
ed.onMouseDown.add(function(e) { ed.edMouseDown = true; });
ed.onMouseUp.add(function(e) { ed.edMouseDown = false; });
ed.onInit.add(function(ed, o) {
tinymce.dom.Event.add(ed.getBody().parentNode, 'dragdrop', function(e) { ed.edMouseDown = false; });
});
// watch for drag attempts
var evt = (tinymce.isIE) ? 'dragenter' : 'dragover'; // IE allows dragdrop reject on dragenter (more efficient)
ed.onInit.add(function(ed, o) {
// use parentNode to go above editor content, to cover entire editor area
tinymce.dom.Event.add(ed.getBody().parentNode, evt, function (e) {
if (!ed.edMouseDown) {
// disallow drop
return tinymce.dom.Event.cancel(e);
}
});
});
}
},
/**
* Returns information about the plugin as a name/value array.
* The current keys are longname, author, authorurl, infourl and version.
*
* @return {Object} Name/value array containing information about the plugin.
*/
getInfo : function() {
return {
longname : 'Advanced Image Resize Helper',
author : 'Marc Hodgins',
authorurl : 'http://www.hodginsmedia.com',
infourl : 'http://code.google.com/p/tinymce-plugin-advimagescale',
version : '1.1.3'
};
}
});
// Register plugin
tinymce.PluginManager.add('advimagescale', tinymce.plugins.AdvImageScale);
/**
* Store image dimensions, pre-resize
*
* @param {object} el HTMLDomNode
*/
function storeDimensions(ed, el) {
var dom = ed.dom;
var elId = dom.getAttrib(el, 'mce_advimageresize_id');
// store original dimensions if this is the first resize of this element
if (!ed.originalDimensions[elId]) {
ed.originalDimensions[elId] = ed.lastDimensions[elId] = {width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height)};
}
return true;
}
/**
* Prepare image for resizing
* Check to see if we've seen this IMG tag before; does tasks such as adding
* unique IDs to image tags, saving "original" image dimensions, etc.
* @param {object} e is optional
*/
function prepareImage(ed, el) {
var dom = ed.dom;
var elId = dom.getAttrib(el, 'mce_advimageresize_id');
// is this the first time this image tag has been seen?
if (!elId) {
var elId = ed.id + "_" + ed.dom.uniqueId();
dom.setAttrib(el, 'mce_advimageresize_id', elId);
storeDimensions(ed, el);
}
return elId;
}
/**
* Adjusts width and height to keep within min/max bounds and also maintain aspect ratio
* If mce_noresize attribute is set to image tag, then image resize is disallowed
*/
function constrainSize(ed, el, e) {
var dom = ed.dom;
var elId = prepareImage(ed, el); // also calls storeDimensions
var resized = (dom.getAttrib(el, 'width') != ed.lastDimensions[elId].width || dom.getAttrib(el, 'height') != ed.lastDimensions[elId].height);
if (!resized)
return; // nothing to do
// disallow image resize if mce_noresize or the noresize class is set on the image tag
if (dom.getAttrib(el, 'mce_noresize') || dom.hasClass(el, ed.getParam('advimagescale_noresize_class', 'noresize')) || ed.getParam('advimagescale_noresize_all')) {
dom.setAttrib(el, 'width', ed.lastDimensions[elId].width);
dom.setAttrib(el, 'height', ed.lastDimensions[elId].height);
if (tinymce.isGecko)
fixGeckoHandles(ed);
return;
}
// Both IE7 and Gecko (as of FF3.0.03) has a "expands image by border width" bug before doing anything else
if (ed.getParam('advimagescale_fix_border_glitch', true /* default to true */)) {
fixImageBorderGlitch(ed, el);
storeDimensions(ed, el); // store adjusted dimensions
}
// filter by regexp so only some images get constrained
var src_filter = ed.getParam('advimagescale_filter_src');
if (src_filter) {
var r = new RegExp(src_filter);
if (!el.src.match(r)) {
return; // skip this element
}
}
// allow filtering by classname
var class_filter = ed.getParam('advimagescale_filter_class');
if (class_filter) {
if (!dom.hasClass(el, class_filter)) {
return; // skip this element, doesn't have the class we want
}
}
// populate new dimensions object
var newDimensions = { width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height) };
// adjust w/h to maintain aspect ratio
if (ed.getParam('advimagescale_maintain_aspect_ratio', true /* default to true */)) {
newDimensions = maintainAspect(ed, el, newDimensions.width, newDimensions.height);
}
// enforce minW/minH/maxW/maxH
newDimensions = checkBoundaries(ed, el, newDimensions.width, newDimensions.height);
// was an adjustment made?
var adjusted = (dom.getAttrib(el, 'width', el.width) != newDimensions.width || dom.getAttrib(el, 'height', el.height) != newDimensions.height);
// apply new w/h
if (adjusted) {
dom.setAttrib(el, 'width', newDimensions.width);
dom.setAttrib(el, 'height', newDimensions.height);
if (tinymce.isGecko) fixGeckoHandles(ed);
}
if (ed.getParam('advimagescale_append_to_url')) {
appendToUri(ed, el, dom.getAttrib(el, 'width', el.width), dom.getAttrib(el, 'height', el.height));
}
// was the image resized?
if (ed.lastDimensions[elId].width != dom.getAttrib(el, 'width', el.width) || ed.lastDimensions[elId].height != dom.getAttrib(el, 'height', el.height)) {
// call "image resized" callback (if set)
if (ed.getParam('advimagescale_resize_callback')) {
ed.getParam('advimagescale_resize_callback')(ed, el);
}
}
// remember "last dimensions" for next time
ed.lastDimensions[elId] = { width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height) };
}
/**
* Fixes IE7 and Gecko border width glitch
*
* Both "add" the border width to an image after the resize handles have been
* dropped. This reverses it by looking at the "previous" known size and comparing
* to the current size. If they don't match, then a resize has taken place and the browser
* has (probably) messed it up. So, we reverse it. Note, this will probably need to be
* wrapped in a conditional statement if/when each browser fixes this bug.
*/
function fixImageBorderGlitch(ed, el) {
var dom = ed.dom;
var elId = dom.getAttrib(el, 'mce_advimageresize_id');
var currentWidth = dom.getAttrib(el, 'width', el.width);
var currentHeight = dom.getAttrib(el, 'height', el.height);
var adjusted = false;
// if current dimensions do not match what we last saw, then a resize has taken place
if (currentWidth != ed.lastDimensions[elId].width) {
var adjustWidth = 0;
// get computed border left/right widths
adjustWidth += parseInt(dom.getStyle(el, 'borderLeftWidth', 'borderLeftWidth'));
adjustWidth += parseInt(dom.getStyle(el, 'borderRightWidth', 'borderRightWidth'));
// reset the width height to NOT include these amounts
if (adjustWidth > 0) {
dom.setAttrib(el, 'width', (currentWidth - adjustWidth));
adjusted = true;
}
}
if (currentHeight != ed.lastDimensions[elId].height) {
var adjustHeight = 0;
// get computed border top/bottom widths
adjustHeight += parseInt(dom.getStyle(el, 'borderTopWidth', 'borderTopWidth'));
adjustHeight += parseInt(dom.getStyle(el, 'borderBottomWidth', 'borderBottomWidth'));
if (adjustHeight > 0) {
dom.setAttrib(el, 'height', (currentHeight - adjustHeight));
adjusted = true;
}
}
if (adjusted && tinymce.isGecko) fixGeckoHandles(ed);
}
/**
* Fix gecko resize handles glitch
*/
function fixGeckoHandles(ed) {
ed.execCommand('mceRepaint', false);
}
/**
* Set image dimensions on into a uri as querystring params
*/
function appendToUri(ed, el, w, h) {
var dom = ed.dom;
var uri = dom.getAttrib(el, 'src');
var wKey = ed.getParam('advimagescale_url_width_key', 'w');
uri = setQueryParam(uri, wKey, w);
var hKey = ed.getParam('advimagescale_url_height_key', 'h');
uri = setQueryParam(uri, hKey, h);
// no need to continue if URL didn't change
if (uri == dom.getAttrib(el, 'src')) {
return;
}
// trigger image loading callback (if set)
if (ed.getParam('advimagescale_loading_callback')) {
// call loading callback
ed.getParam('advimagescale_loading_callback')(el);
}
// hook image load(ed) callback (if set)
if (ed.getParam('advimagescale_loaded_callback')) {
// hook load event on the image tag to call the loaded callback
tinymce.dom.Event.add(el, 'load', imageLoadedCallback, {el: el, ed: ed});
}
// set new src
dom.setAttrib(el, 'src', uri);
}
/**
* Callback event when an image is (re)loaded
* @param {object} e Event (use e.target or this.el to access element, this.ed to access editor instance)
*/
function imageLoadedCallback(e) {
var el = this.el; // image element
var ed = this.ed; // editor
var callback = ed.getParam('advimagescale_loaded_callback'); // user specified callback
// call callback, pass img as param
callback(el);
// remove callback event
tinymce.dom.Event.remove(el, 'load', imageLoadedCallback);
}
/**
* Sets URL querystring parameters by appending or replacing existing params of same name
*/
function setQueryParam(uri, key, value) {
if (!uri.match(/\?/)) uri += '?';
if (!uri.match(new RegExp('([\?&])' + key + '='))) {
if (!uri.match(/[&\?]$/)) uri += '&';
uri += key + '=' + escape(value);
} else {
uri = uri.replace(new RegExp('([\?\&])' + key + '=[^&]*'), '$1' + key + '=' + escape(value));
}
return uri;
}
/**
* Returns w/h that maintain aspect ratio
*/
function maintainAspect(ed, el, w, h) {
var elId = ed.dom.getAttrib(el, 'mce_advimageresize_id');
// calculate aspect ratio of original so we can maintain it
var ratio = ed.originalDimensions[elId].width / ed.originalDimensions[elId].height;
// decide which dimension changed more (percentage), because that's the
// one we'll respect (the other we'll adjust to keep aspect ratio)
var lastW = ed.lastDimensions[elId].width;
var lastH = ed.lastDimensions[elId].height;
var deltaW = Math.abs(lastW - w); // absolute
var deltaH = Math.abs(lastH - h); // absolute
var pctW = Math.abs(deltaW / lastW); // percentage
var pctH = Math.abs(deltaH / lastH); // percentage
if (deltaW || deltaH) {
if (pctW > pctH) {
// width changed more - use that as the locked point and adjust height
return { width: w, height: Math.round(w / ratio) };
} else {
// height changed more - use that as the locked point and adjust width
return { width: Math.round(h * ratio), height: h };
}
}
// nothing changed
return { width: w, height: h };
}
/**
* Enforce min/max boundaries
*
* Returns true if an adjustment was made
*/
function checkBoundaries(ed, el, w, h) {
var elId = ed.dom.getAttrib(el, 'mce_advimageresize_id');
var maxW = ed.getParam('advimagescale_max_width');
var maxH = ed.getParam('advimagescale_max_height');
var minW = ed.getParam('advimagescale_min_width');
var minH = ed.getParam('advimagescale_min_height');
var maintainAspect = ed.getParam('advimagescale_maintain_aspect_ratio', true);
var oW = ed.originalDimensions[elId].width;
var oH = ed.originalDimensions[elId].height;
var ratio = oW/oH;
// max
if (maxW && w > maxW) {
w = maxW;
h = maintainAspect ? Math.round(w / ratio) : h;
}
if (maxH && h > maxH) {
h = maxH;
w = maintainAspect ? Math.round(h * ratio) : w;
}
// min
if (minW && w < minW) {
w = minW;
h = maintainAspect ? Math.round(w / ratio) : h;
}
if (minH && h < minH) {
h = minH;
w = maintainAspect ? Math.round(h * ratio) : h;
}
return { width: w, height:h };
}
})();