/*
* Copyright 2013 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Class to handle image insertion in inline editor
* @private
*/
Ext.define('Ametys.plugins.cms.editor.Images', {
singleton: true,
/**
* Insert a local image
* @param {Ametys.cms.editor.EditorButtonController} controller The controller calling this function
*/
insertLocalImage : function (controller)
{
var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
var contentTarget = message.getTarget(function(target) { return target.getId() == "content" });
if (contentTarget != null)
{
var formTarget = contentTarget.getSubtarget(function(target) { return target.getId() == "form" });
if (formTarget != null)
{
var form = formTarget.getParameters()['object'];
var fieldTarget = formTarget.getSubtarget(function(target) { return target.getId() == "field" });
if (fieldTarget != null)
{
var field = form.findField(fieldTarget.getParameters()['name']);
this._insertLocalImage(field.getEditor(), controller.getInitialConfig('icon-glyph'));
}
}
}
},
/**
* Insert a local image
* @param {tinymce.Editor} editor The richtext editor
* @param {String} glyph The glyph to use
* @param {String} [uploadUrl] The URL to use for upload. Can be null to use the default one.
* @param {Object} [cropUrl] The URL object (containing plugin & url) to use for cropping. Can be null to use the default one.
* @private
*/
_insertLocalImage : function (editor, glyph, uploadUrl, cropUrl)
{
Ametys.helper.FileUpload.open({
iconCls: glyph,
title: "{{i18n PLUGINS_CMS_EDITOR_IMAGES_INSERT_IMAGE}}",
helpmessage: "{{i18n PLUGINS_CMS_EDITOR_IMAGES_BROWSE_DESC}}",
callback: afterUpload,
autovalidate: autoValidate,
filter: Ametys.helper.FileUpload.IMAGE_FILTER,
uploadUrl: uploadUrl
});
var afterCropFn = Ext.bind(this._insertLocalImageCb, this, [editor], 0);
function autoValidate(filename)
{
return /\.jpg|\.jpeg|\.gif|\.png$/i.test(filename);
}
function afterUpload(id, filename, fileSize, viewHref, downloadHref)
{
if (autoValidate(filename))
{
Ext.create('Ametys.helper.CropDialog', {
imageId: id,
imageName: filename,
imageSize: fileSize,
imageViewHref: viewHref,
imageDownloadHref: downloadHref,
cropUrl: cropUrl,
afterCropFn: afterCropFn
}).show();
}
else
{
afterCropFn(id, filename, fileSize, viewHref, downloadHref);
}
}
},
/**
* Callback function called after uploading local image
* @param {tinymce.Editor} editor The richtext editor
* @param {String} id Id of the uploading image
* @param {String} filename File name of the uploading image
* @param {String} size File length of the uploading image
* @param {String} viewHref The URL to view the uploaded image
* @param {String} downloadHref The URL to download the uploaded image
* @private
*/
_insertLocalImageCb: function(editor, id, filename, size, viewHref, downloadHref)
{
editor.focus();
if (id != null)
{
var data = {
"id": Ext.id(),
"class": tinyMCE.activeEditor.schema.elements.img ? tinyMCE.activeEditor.schema.elements.img.attributes.class.defaultValue : "",
"src": viewHref,
"_mce_ribbon_select": "1",
"data-ametys-type": "temp",
"data-ametys-temp-src": id
};
editor.selection.setContent(editor.dom.createHTML('img', data));
var maxWidth = editor.getBody().offsetWidth * 0.9;
var node = editor.dom.get(data.id);
var editor = tinyMCE.activeEditor;
node.onload = function() {
node.onload = null;
var imgWidth = node.offsetWidth;
var imgHeight = node.offsetHeight;
if (imgWidth > maxWidth)
{
node.setAttribute("width", maxWidth);
node.setAttribute("height", Math.round(100 * imgHeight * maxWidth / imgWidth) / 100.0);
}
editor.selection.select(node);
}
node.removeAttribute("id");
}
},
/**
* Insert a resource image (from the resources explorer)
* @param controller {Ametys.cms.editor.EditorButtonController} controller The controller calling this function
*/
insertResourceImage : function (controller)
{
var iconCls = [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' ');
this._insertResourceImage(iconCls, true);
},
/**
* Insert a resource image (from the resources explorer)
* @param {String} iconCls icon class
* @param {Boolean} allowDragAndDrop True to allow drag and drop
*/
_insertResourceImage : function (iconCls, allowDragAndDrop)
{
Ametys.cms.uihelper.ChooseResource.open({
iconCls: iconCls,
title: "{{i18n PLUGINS_CMS_EDITOR_RESOURCE_IMAGE_INSERT_LABEL}}",
helpmessage: "{{i18n PLUGINS_CMS_EDITOR_RESOURCE_IMAGE_BROWSER_DESC}}",
callback: this._insertResourceImageCb,
allowDragAndDrop: allowDragAndDrop,
filter: Ametys.explorer.tree.ExplorerTree.IMAGE_FILTER
});
},
/**
* Callback function called after uploading a resource image
* @param {String} id Id of the uploading image
* @param {String} filename File name of the uploaded image
* @param {String} size File length of the uploaded image
* @param {String} viewHref The URL to view the uploaded image
* @param {String} downloadHref The URL to download the uploaded image
* @private
*/
_insertResourceImageCb: function(id, filename, size, viewHref, downloadHref)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
if (id != null)
{
var data = {
"id": Ext.id(),
"class": tinyMCE.activeEditor.schema.elements.img ? tinyMCE.activeEditor.schema.elements.img.attributes.class.defaultValue : "",
"src": viewHref,
"_mce_ribbon_select": "1",
"data-ametys-type": "explorer",
"data-ametys-src": id
};
tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('img', data));
var node = tinyMCE.activeEditor.dom.get(data.id);
var editor = tinyMCE.activeEditor;
node.onload = function() {
node.onload = null;
editor.selection.select(node);
}
node.removeAttribute("id");
}
},
// ---------------------------------------- //
// WIDTH //
// ---------------------------------------- //
/**
* Set the width of current <img> node when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setImageWidthOnBlur: function(input)
{
var inputValue = input.getValue();
var img = this._currentNode;
if (img != null && img.style.width != inputValue + 'px')
{
this.setWidth(inputValue);
}
},
/**
* Set the width of current <img> node when the spinner is made to spin up or down
* @param {Ext.form.field.Text} input The input text
* @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
*/
setImageWidthOnSpin: function(input, direction)
{
this.setWidth(input.getValue() + (direction == 'up' ? 1 : -1));
},
/**
* Set the width of current <img> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setImageWidthOnSpecialKey: function(input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setWidth(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
var img = this._currentNode;
input.setValue(img.style.width);
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set 'width' style to current <img> node
* @param {String} value The value
*/
setWidth: function (value)
{
var img = this._currentNode;
if (img != null)
{
var ratio = img.getAttribute("data-ratio");
if (!ratio)
{
ratio = tinyMCE.activeEditor.dom.get(img).offsetHeight / tinyMCE.activeEditor.dom.get(img).offsetWidth;
img.setAttribute("data-ratio", ratio);
}
img.style.width = value + "px";
img.style.height = Math.round(value*ratio) + "px";
img.removeAttribute("data-mce-style");
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected image
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
widthListener: function (controller, field, node)
{
if (node)
{
controller.setValue (field.getEditor().dom.get(node).offsetWidth);
}
else
{
controller.setValue ('');
}
this._currentNode = node;
},
// ---------------------------------------- //
// HEIGHT //
// ---------------------------------------- //
/**
* Set the height of current <img> node when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setImageHeightOnBlur: function(input)
{
var inputValue = input.getValue();
var img = this._currentNode;
if (img != null && img.style.height != inputValue + 'px')
{
this.setHeight(inputValue);
}
},
/**
* Set the height of current <img> node when the spinner is made to spin up or down
* @param {Ext.form.field.Text} input The input text
* @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
*/
setImageHeightOnSpin: function(input, direction)
{
this.setHeight(input.getValue() + (direction == 'up' ? 1 : -1));
},
/**
* Set the height of current <img> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setImageHeightOnSpecialKey: function(input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setHeight(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
var img = this._currentNode;
input.setValue(img.style.height);
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set 'height' style to current <img> node
* @param {String} value The value
*/
setHeight: function (value)
{
var img = this._currentNode;
if (img != null)
{
var ratio = img.getAttribute("data-ratio");
if (!ratio)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
ratio = tinyMCE.activeEditor.dom.get(img).offsetHeight / tinyMCE.activeEditor.dom.get(img).offsetWidth;
img.setAttribute("data-ratio", ratio);
}
img.style.width = Math.round(value/ratio) + "px";
img.style.height = value + "px";
img.removeAttribute("data-mce-style");
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected image
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
heightListener: function (controller, field, node)
{
if (node)
{
controller.setValue (field.getEditor().dom.get(node).offsetHeight);
}
else
{
controller.setValue ('');
}
this._currentNode = node;
},
// ---------------------------------------- //
// LEGEND //
// ---------------------------------------- //
/**
* Set 'title' attribute to current <img> node when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setLegendOnBlur: function (input)
{
var inputValue = input.getValue();
if (this._getLegend(this._currentNode) != inputValue)
{
this.setLegend(inputValue);
}
},
/**
* Set 'title' attribute to current <img> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setLegendOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setLegend(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getLegend(this._currentNode));
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set 'title' attribute to current <img> node when the input loses the focus
* @param {String} value The value
*/
setLegend: function (value)
{
var img = this._currentNode;
if (img != null)
{
if (value == '')
{
img.removeAttribute("title");
}
else
{
img.title = value;
}
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Get the 'title' attribute of a <img> node
* @param {HTMLElement} node The <img> node.
* @return {String} The title attribute
* @private
*/
_getLegend: function (node)
{
return node.title || "";
},
/**
* Enable/disable controller and set input value according the selected image
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
legendListener: function (controller, field, node)
{
if (node)
{
controller.setValue (this._getLegend(node));
}
else
{
controller.setValue ('');
}
this._currentNode = node;
},
// ---------------------------------------- //
// ALTERNATIVE //
// ---------------------------------------- //
/**
* Set 'alt' attribute to current <img> node
* @param {Ext.form.field.Text} input The input text
*/
setAlternativeOnBlur: function (input)
{
var inputValue = input.getValue();
if (this._getAlternative(this._currentNode) != inputValue)
{
this.setAlternative(input.getValue());
}
},
/**
* Set 'alt' attribute to current <img> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setAlternativeOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setAlternative(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getAlternative(this._currentNode));
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set 'alt' attribute to current <img> node
* @param {String} value The value
*/
setAlternative: function (value)
{
var img = this._currentNode;
if (img != null)
{
if (value == '')
{
img.removeAttribute("alt");
}
else
{
img.alt = value;
}
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Get the 'alt' attribute of a <img> node
* @param {HTMLElement} node The <img> node.
* @return {String} The alt attribute
* @private
*/
_getAlternative: function (node)
{
return node.alt || "";
},
/**
* Enable/disable controller and set input value according the selected image
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
alternativeListener: function (controller, field, node)
{
if (node)
{
controller.setValue (this._getAlternative(node));
}
else
{
controller.setValue ('');
}
this._currentNode = node;
},
// ---------------------------------------- //
// FLOAT //
// ---------------------------------------- //
/**
* Align image on the text left
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
applyFloatLeft: function (controller)
{
if (this._currentNode != null)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.dom.addClass(this._currentNode, 'floatleft');
tinyMCE.activeEditor.dom.removeClass(this._currentNode, 'floatright');
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Align image on the text right
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
applyFloatRight: function (controller)
{
if (this._currentNode != null)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.dom.addClass(this._currentNode, 'floatright');
tinyMCE.activeEditor.dom.removeClass(this._currentNode, 'floatleft');
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Align image with the text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
applyNoFloat: function (controller)
{
if (this._currentNode != null)
{
tinyMCE.activeEditor.focus();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.dom.removeClass(this._currentNode, 'floatleft');
tinyMCE.activeEditor.dom.removeClass(this._currentNode, 'floatright');
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Enable/disable and toggle/untoggle controller according the image alignment
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
floatControllerListener: function (controller, field, node)
{
var editor = (node != null && field != null) ? field.getEditor() : null;
var cssClass = controller.getInitialConfig('css-class');
var toggleFunction = Ext.bind(controller.toggle, controller);
var enableFunction = function(enable) {if (enable) {controller.enable()} else {controller.disable()}};
this.floatListener(node, editor, toggleFunction, enableFunction, cssClass);
},
/**
* Enable/disable and toggle/untoggle controller according the image alignment
* @param {HTMLElement} node The current selected node. Can be null.
* @param {Object} editor The tinyMce editor
* @param {Function} toggleFunction function called with true/false to select/unselect button
* @param {Function} enableFunction function called with true/false to enable/disable button
* @param {String} cssClass The class that have to be there
*/
floatListener: function (node, editor, toggleFunction, enableFunction, cssClass)
{
if (node)
{
var isActive = editor.dom.hasClass(node, cssClass);
toggleFunction(isActive);
enableFunction(true);
}
else
{
toggleFunction(false);
enableFunction(true);
}
},
/**
* Enable/disable and toggle/untoggle controller according the image alignment
* @param {Ametys.ribbon.element.ui.ButtonController}controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
noFloatControllerListener: function (controller, field, node)
{
var editor = (node != null && field != null) ? field.getEditor() : null;
var cssClass = ['floatleft', 'floatright'];
var toggleFunction = Ext.bind(controller.toggle, controller);
var enableFunction = function(enable) {if (enable) {controller.enable()} else {controller.disable()}};
this.noFloatListener(node, editor, toggleFunction, enableFunction, cssClass);
},
/**
* Enable/disable and toggle/untoggle controller according the image alignment
* @param {HTMLElement} node The current selected node. Can be null.
* @param {Object} editor The tinyMce editor
* @param {Function} toggleFunction function called with true/false to select/unselect button
* @param {Function} enableFunction function called with true/false to enable/disable button
* @param {String[]} cssClass The class that have to be absent
*/
noFloatListener: function (node, editor, toggleFunction, enableFunction, cssClass)
{
if (node)
{
var isActive = true;
for (var i = 0; i < cssClass.length; i++) {
isActive = isActive && !editor.dom.hasClass(node, cssClass[i]);
}
toggleFunction(isActive);
enableFunction(true);
}
else
{
toggleFunction(false);
enableFunction(true);
}
},
/**
* Enable/disable controller if the current selected node is an image
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
imageSelectionListener: function (controller, field, node)
{
this._currentNode = node;
},
// ---------------------------------------- //
// ZOOMABLE //
// ---------------------------------------- //
/**
* Add or remove the "zoomable" attribute on the current image node
* @param {Ext.form.field.Checkbox} input The checkbox input field
*/
setZoomableOnChange: function (input)
{
var img = this._currentNode;
if (img != null)
{
var newValue = input.getValue();
if (newValue != img.hasAttribute('zoomable'))
{
if (newValue)
{
img.setAttribute("zoomable", "");
}
else
{
img.removeAttribute("zoomable");
}
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
tinyMCE.activeEditor.focus();
}
},
/**
* Enable/disable controller and check box if the current selected node is a zoomable image
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
zoomableListener: function (controller, field, node)
{
if (node)
{
controller.setValue(node.hasAttribute("zoomable"));
}
else
{
controller.setValue(false);
}
this._currentNode = node;
}
});