/*
* Copyright 2017 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 media insertion in inline editor
* @private
*/
Ext.define('Ametys.plugins.inlinemedia.Media', {
singleton: true,
/**
* Insert a Youtube video
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertYoutube: function(controller)
{
var editor = this._getEditor();
if (editor != null)
{
this._insertYoutube(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
}
},
/**
* Insert a Youtube video
* @param {tinymce.Editor} editor The richtext editor
* @param {String} glyph The glyph to use
* @private
*/
_insertYoutube: function(editor, glyph)
{
Ametys.plugins.inlinemedia.InsertMediaHelper.open(
editor,
"{{i18n PLUGINS_INLINEMEDIA_YOUTUBE_DIALOG_TITLE}}",
glyph,
"{{i18n PLUGINS_INLINEMEDIA_YOUTUBE_DIALOG_MESSAGE}}",
"youtube",
[ "[^\?\&\#]*v=([^\?\&\#]+)", "[^\?\&\#]*youtu.be\/([^\?\&\#]+)" ],
480,
385
);
},
/**
* Insert a Dailymotion video
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertDailymotion: function(controller)
{
var editor = this._getEditor();
if (editor != null)
{
this._insertDailymotion(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
}
},
/**
* Insert a Dailymotion video
* @param {tinymce.Editor} editor The richtext editor
* @param {String} glyph The glyph to use
* @private
*/
_insertDailymotion: function(editor, glyph)
{
Ametys.plugins.inlinemedia.InsertMediaHelper.open(
editor,
"{{i18n PLUGINS_INLINEMEDIA_DAILYMOTION_DIALOG_TITLE}}",
glyph,
"{{i18n PLUGINS_INLINEMEDIA_DAILYMOTION_DIALOG_MESSAGE}}",
"dailymotion",
[ "[^\?\&\#]*\/video\/([a-zA-Z0-9]+).*" ],
480,
360
);
},
/**
* Insert a local video
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertLocalVideo : function (controller)
{
var editorInfos = this._getEditor();
this._insertLocalVideo(editorInfos, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
},
/**
* Insert a local vdeo
* @param {tinymce.Editor} editor The richtext editor
* @param {String} glyph The glyph to use
* @private
*/
_insertLocalVideo : function (editor, glyph, uploadUrl)
{
var afterUpload = Ext.bind(this._insertLocalVideoCb, this, [editor], 0);
Ametys.helper.FileUpload.open({
iconCls: glyph,
title: "{{i18n PLUGINS_INLINEMEDIA_LOCAL_DIALOG_TITLE}}",
helpmessage: "{{i18n PLUGINS_INLINEMEDIA_LOCAL_DIALOG_MESSAGE}}",
callback: afterUpload,
uploadUrl: uploadUrl,
allowedExtensions: ['ogv', 'ogg', 'mp4', 'webm']
});
},
/**
* Callback function called after uploading local video
* @param {tinymce.Editor} editor The richtext editor
* @param {String} id Id of the uploading video
* @param {String} filename File name of the uploading video
* @param {String} size File length of the uploading video
* @param {String} viewHref The URL to view the uploaded video
* @param {String} downloadHref The URL to download the uploaded video
* @private
*/
_insertLocalVideoCb: function(editor, id, filename, size, viewHref, downloadHref)
{
editor.focus();
if (id != null)
{
var tmpId = Ext.id();
editor.execCommand('mceBeginUndoLevel');
editor.execCommand('mceInsertContent', false, '<img id="' + tmpId + '" class="video" _mce_ribbon_select="1" marker="marker" media="video" width="300" height="200" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-type="temp" data-ametys-temp-src="' + id + '"/>');
editor.execCommand('mceEndUndoLevel');
var node = editor.dom.get(tmpId);
node.removeAttribute("id");
editor.selection.select(node);
}
},
/**
* Insert a resource video (from the resources explorer)
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertResourceVideo : function (controller)
{
var editor = this._getEditor();
if (editor != null)
{
this._insertResourceVideo(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '), true);
}
},
/**
* Insert a resource video (from the resources explorer)
* @param {tinymce.Editor} editor The richtext editor
* @param {String} iconCls icon class
* @param {Boolean} allowDragAndDrop True to allow drag and drop
*/
_insertResourceVideo : function (editor, glyph, allowDragAndDrop)
{
var afterUpload = Ext.bind(this._insertResourceVideoCb, this, [editor], 0);
Ametys.cms.uihelper.ChooseResource.open({
iconCls: glyph,
title: "{{i18n PLUGINS_INLINEMEDIA_RESSOURCES_DIALOG_TITLE}}",
helpmessage: "{{i18n PLUGINS_INLINEMEDIA_RESSOURCES_DIALOG_MESSAGE}}",
callback: afterUpload,
allowDragAndDrop: allowDragAndDrop,
filter: Ametys.explorer.tree.ExplorerTree.VIDEO_FILTER
});
},
/**
* Callback function called after uploading a resource video
* @param {String} id Id of the uploading video
* @param {String} filename File name of the uploaded video
* @param {String} size File length of the uploaded video
* @param {String} viewHref The URL to view the uploaded video
* @param {String} downloadHref The URL to download the uploaded video
* @private
*/
_insertResourceVideoCb: function(editor, id, filename, size, viewHref, downloadHref)
{
editor.focus();
if (id != null)
{
editor.execCommand('mceBeginUndoLevel');
editor.execCommand('mceInsertContent', false, '<img class="video" _mce_ribbon_select="1" marker="marker" media="video" width="300" height="200" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-src="' + id + '" data-ametys-type="explorer" />');
editor.execCommand('mceEndUndoLevel');
}
},
/**
* Insert a local audio
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertLocalAudio : function (controller)
{
Ametys.helper.FileUpload.open({
iconCls: [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '),
title: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_LOCAL_DIALOG_TITLE}}",
helpmessage: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_LOCAL_DIALOG_MESSAGE}}",
callback: this._insertLocalAudioCb,
allowedExtensions: ['mp3', 'oga', 'ogg', 'wav']
});
},
/**
* Callback function called after uploading local audio
* @param {String} id Id of the uploading audio
* @param {String} filename File name of the uploading audio
* @param {String} size File length of the uploading audio
* @param {String} viewHref The URL to view the uploaded audio
* @param {String} downloadHref The URL to download the uploaded audio
* @private
*/
_insertLocalAudioCb: 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 message = Ametys.message.MessageBus.getCurrentSelectionMessage();
var contentTarget = message.getTarget(function(target) { return target.getId() == "content" });
if (contentTarget != null)
{
var fieldTarget = contentTarget.getSubtarget(function(target) { return target.getId() == "field" });
if (fieldTarget != null)
{
var tmpId = Ext.id();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.execCommand('mceInsertContent', false, '<img id="' + tmpId + '" class="audio" _mce_ribbon_select="1" marker="marker" media="audio" width="300" height="40" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-type="temp" data-ametys-temp-src="' + id + '"/>');
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
var node = tinyMCE.activeEditor.dom.get(tmpId);
node.removeAttribute("id");
tinyMCE.activeEditor.selection.select(node);
}
}
}
},
/**
* Insert a resource audio (from the resources explorer)
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
insertResourceAudio : function (controller)
{
Ametys.cms.uihelper.ChooseResource.open({
iconCls: [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '),
title: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_RESSOURCES_DIALOG_TITLE}}",
helpmessage: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_RESSOURCES_DIALOG_MESSAGE}}",
callback: this._insertResourceAudioCb,
filter: Ametys.explorer.tree.ExplorerTree.SOUND_FILTER
});
},
/**
* Callback function called after uploading a resource audio
* @param {String} id Id of the uploading audio
* @param {String} filename File name of the uploaded audio
* @param {String} size File length of the uploaded audio
* @param {String} viewHref The URL to view the uploaded audio
* @param {String} downloadHref The URL to download the uploaded audio
* @private
*/
_insertResourceAudioCb: function(id, filename, size, viewHref, downloadHref)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
if (id != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.execCommand('mceInsertContent', false, '<img class="audio" _mce_ribbon_select="1" marker="marker" media="audio" width="300" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-src="' + id + '" data-ametys-type="explorer" />');
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Get the image node from current cursor position
* @return {HTMLElement} The image node or null if not found
* @private
*/
_getMedia: function(editor, node)
{
if (node != null)
{
var marker = node.getAttribute("marker");
var media = node.getAttribute("media");
if (marker != 'marker' || !media)
{
node = null;
}
}
return node;
},
// ---------------------------------------- //
// WIDTH //
// ---------------------------------------- //
/**
* Set the width of current <img> node when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setMediaWidthOnBlur: function(input)
{
this.setWidth(input.getValue());
},
/**
* 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.
*/
setMediaWidthOnSpin: 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
*/
setMediaWidthOnSpecialKey: 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();
input.setValue(this._currentNode.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)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
ratio = tinyMCE.activeEditor.dom.get(img).height / tinyMCE.activeEditor.dom.get(img).width;
img.setAttribute("data-ratio", ratio);
}
img.style.width = value + "px";
img.style.height = Math.round(value*ratio) + "px";
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected 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.
*/
widthListener: function (controller, field, node)
{
var img = field != null && node != null ? this._getMedia(field.getEditor(), node) : null;
if (img)
{
controller.setValue (controller.getCurrentField().getEditor().dom.get(img).width);
}
else
{
controller.disable();
controller.setValue ('');
}
this._currentNode = img;
},
// ---------------------------------------- //
// HEIGHT //
// ---------------------------------------- //
/**
* Set the height of current <img> node when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setMediaHeightOnBlur: function(input)
{
this.setHeight(input.getValue());
},
/**
* 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.
*/
setMediaHeightOnSpin: 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
*/
setMediaHeightOnSpecialKey: 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();
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
input.setValue(tinyMCE.activeEditor.dom.get(this._currentNode).height);
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)
{
ratio = tinyMCE.activeEditor.dom.get(img).height / tinyMCE.activeEditor.dom.get(img).width;
img.setAttribute("data-ratio", ratio);
}
img.style.width = Math.round(value/ratio) + "px";
img.style.height = value + "px";
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected media
* @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.
*/
heightListener: function (controller, field, node)
{
var img = field != null && node != null ? this._getMedia(field.getEditor(), node) : null;
if (img)
{
controller.setValue (field.getEditor().dom.get(img).height);
}
else
{
controller.disable();
controller.setValue ('');
}
this._currentNode = img;
},
// ---------------------------------------- //
// ALTERNATIVE //
// ---------------------------------------- //
/**
* Set 'alt' attribute to current <img> node
* @param {Ext.form.field.Text} input The input text
*/
setAlternativeOnBlur: function (input)
{
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());
// 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;
}
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Get the 'alt' attribute of a <img> node
* @param {HTMLElement} node The <img> node. Can be null to retrieve <img> node from cursor current position
* @return {String} The alt attribute
* @private
*/
_getAlternative: function (node)
{
return node.alt || "";
},
/**
* Enable/disable controller and set input value according the selected media
* @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)
{
var img = field != null && node != null ? this._getMedia(field.getEditor(), node) : null;
if (img)
{
controller.setValue (this._getAlternative(img));
}
else
{
controller.disable();
controller.setValue ('');
}
this._currentNode = img;
},
// ---------------------------------------- //
// 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 && controller.getCurrentField() != null)
{
var editor = controller.getCurrentField().getEditor();
editor.execCommand('mceBeginUndoLevel');
editor.dom.addClass(this._currentNode, "floatleft");
editor.dom.removeClass(this._currentNode, "floatright");
editor.execCommand('mceEndUndoLevel');
editor.focus();
}
},
/**
* 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 && controller.getCurrentField() != null)
{
var editor = controller.getCurrentField().getEditor();
editor.execCommand('mceBeginUndoLevel');
editor.dom.addClass(this._currentNode, "floatright");
editor.dom.removeClass(this._currentNode, "floatleft");
editor.execCommand('mceEndUndoLevel');
editor.focus();
}
},
/**
* Align image with the text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
applyNoFloat: function (controller)
{
if (this._currentNode != null && controller.getCurrentField() != null)
{
var editor = controller.getCurrentField().getEditor();
editor.execCommand('mceBeginUndoLevel');
editor.dom.removeClass(this._currentNode, "floatright");
editor.dom.removeClass(this._currentNode, "floatleft");
editor.execCommand('mceEndUndoLevel');
editor.focus();
}
},
/**
* 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.
*/
floatListener: function (controller, field, node)
{
var img = field != null && node != null ? this._getMedia(field.getEditor(), node) : null;
if (img)
{
controller.toggle (field.getEditor().dom.hasClass(img, controller.getInitialConfig('css-class')));
}
else
{
controller.toggle (false);
controller.disable();
}
this._currentNode = img;
},
/**
* 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.
*/
noFloatListener: function (controller, field, node)
{
var img = field != null && node != null ? this._getMedia(field.getEditor(), node) : null;
if (img)
{
controller.toggle (!field.getEditor().dom.hasClass(img, 'floatleft') && !field.getEditor().dom.hasClass(img, 'floatright'));
}
else
{
controller.toggle (false);
controller.disable();
}
this._currentNode = img;
},
/**
* Find the tinyMCE editor impacted by this controler
* @return {tinymce.Editor} The editor linked to this controller
* @private
*/
_getEditor : function ()
{
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 = contentTarget.getSubtarget(function(target) { return target.getId() == "field" });
if (fieldTarget != null)
{
var field = form.findField(fieldTarget.getParameters()['name']);
return field.getEditor();
}
}
}
return null;
}
});