/*
* 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 basic styles such as bold, italic, supscript, ...
* @private
*/
Ext.define('Ametys.plugins.cms.editor.BasicStyles', {
singleton: true,
/**
* @protected
* Converts the result of a query command state to a boolean
* @param {Boolean} state Can be null or a boolean
* @returns {Boolean} Non null value.
*/
testQueryCommandState: function(state)
{
return state != undefined && state != false;
},
/**
* @protected
* Move the current tinymce editor selection to an alignable elements.
* Current impl does move selection from an image node to its parent node
* @param {Ametys.cms.form.widget.RichText} field The tinymce editor field into
*/
changeSelectionToAlignableElement: function(field)
{
var n = field.getEditor().selection.getNode();
if (/^img$/i.test(n.tagName))
{
field.getEditor().selection.collapse();
}
},
/**
* @protected
* Determines if the current tinymce editor selection can be manually aligned
* Current impl does test for caption elements
* @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
* @return {boolean} True if #getManuallyAlignement and #setManuallyAlignement can be called
*/
isManuallyAlignableElement: function(field)
{
return /^caption$/i.test(field.getEditor().selection.getNode().tagName);
},
/**
* @protected
* If the current tinymce editor selection #isManuallyAlignableElement, this method get the align value
* @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
* @return {String} The css align value. (empty, 'left', 'right', 'center', 'justify')
*/
getManuallyAlignement: function(field)
{
return field.getEditor().selection.getNode().style.textAlign;
},
/**
* @protected
* If the current tinymce editor selection #isManuallyAlignableElement, this method change the align value
* @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
* @param {String} align The css align value. (empty, 'left', 'right', 'center', 'justify')
* @param {Boolean} [force=true] When force is true, the align value is set. If not, it is toggled
* @param {Boolean} [sendSelect=false] If true, send a mceSelectNode command.
*/
setManuallyAlignement: function(field, align, force, sendSelect)
{
force = force === true;
sendSelect = sendSelect !== false;
if (force || this.getManuallyAlignement(field) != align)
{
field.getEditor().selection.getNode().style.textAlign = align;
}
else
{
field.getEditor().selection.getNode().style.textAlign = "";
}
field.getEditor().selection.getNode().removeAttribute("data-mce-style");
if (sendSelect)
{
field.getEditor().execCommand('mceSelectNode', false, field.getEditor().selection.getNode());
}
}
});
/**
* This singleton class handles bold style
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Bold', {
singleton: true,
/**
* Apply or remove bold style
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("Bold");
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("Bold"));
controller.toggle(state);
off ? controller.disable() : controller.enable();
}
});
/**
* This singleton class handles italic style
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Italic', {
singleton: true,
/**
* Apply or remove italic style
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("Italic");
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("Italic"));
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This singleton class is used to indent text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Indent', {
singleton: true,
/**
* Indent text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("Indent");
field.getEditor().focus();
// check that we are in a list (ordered or not)
if (Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList")))
{
// get the first parent UL with a non-empty class name
var parentUl = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName) && node.className != ''} );
if (parentUl != null)
{
// apply parent class name to indented list
var parentClassName = parentUl.className;
field.getEditor().selection.getNode().parentNode.setAttribute("class", parentUl.className);
}
}
},
/**
* Enable/disable controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var on = false;
if (!off && field.getEditor() != null && field.getEditor().dom != null)
{
// check that we are in a list (ordered or not)
on = Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList"));
if (on)
{
on = false;
// check that we are in a nested list
var parentList = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^li$/i.test(node.tagName) } );
if (parentList != null)
{
var prev = Ext.get(parentList).prev("li");
on = prev != null;
}
}
}
controller.setDisabled(off || !on);
}
});
/**
* This singleton class is used to outdent text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Oudent', {
singleton: true,
/**
* Outdent text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("Outdent");
field.getEditor().focus();
},
/**
* Enable/disable controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var on = false;
if (!off && field.getEditor() != null && field.getEditor().dom != null)
{
// check that we are in a list (ordered or not)
on = Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList"));
if (on)
{
// check that we are in a nested list
var parentList = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName) } );
if (parentList)
{
on = field.getEditor().dom.getParent(parentList.parentNode, function(node) { return /^ul|ol$/i.test(node.tagName) } ) != null;
}
}
}
controller.setDisabled(off || !on);
}
});
/**
* This class is used to "supscript" the text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Sup', {
singleton: true,
/**
* Apply or remove "super script" style
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("SuperScript");
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("SuperScript"));
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to "subscript" the text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.Sub', {
singleton: true,
/**
* Apply or remove "sub script" style
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
field.getEditor().execCommand("SubScript");
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("SubScript"));
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to align text to the left
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignLeft', {
singleton: true,
/**
* Align text to the left
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
{
Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "left");
}
else
{
var carretLocation = field.getEditor().selection.getBookmark();
Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
field.getEditor().execCommand('JustifyLeft');
field.getEditor().selection.moveToBookmark(carretLocation);
}
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null;
var state = !off && (
Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ?
/^left$/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field))
: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyLeft"))
);
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to center text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignCenter', {
singleton: true,
/**
* Center the text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
{
Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "center");
}
else
{
var carretLocation = field.getEditor().selection.getBookmark();
Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
field.getEditor().execCommand('JustifyCenter');
field.getEditor().selection.moveToBookmark(carretLocation);
}
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null;
var state = !off && (
Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ?
/^center$/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field))
: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyCenter"))
);
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to align text to the right
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignRight', {
singleton: true,
/**
* Align text to the right
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
{
Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "right");
}
else
{
var carretLocation = field.getEditor().selection.getBookmark();
Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
field.getEditor().execCommand('JustifyRight');
field.getEditor().selection.moveToBookmark(carretLocation);
}
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null;
var state = !off && (
Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ?
/^right/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field))
: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyRight"))
);
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to justify text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignJustify', {
singleton: true,
/**
* Justify the text
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
apply: function (controller)
{
var field = controller.getCurrentField();
if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
{
Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "justify");
}
else
{
var carretLocation = field.getEditor().selection.getBookmark();
Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
field.getEditor().execCommand('JustifyFull');
field.getEditor().selection.moveToBookmark(carretLocation);
}
field.getEditor().focus();
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
var off = field == null || node == null;
var state = !off && (
Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ?
/^justify/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field))
: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyFull"))
);
controller.toggle(state);
controller.setDisabled(off);
}
});
/**
* This class is used to justify text
* @private
*/
Ext.define('Ametys.plugins.cms.editor.basicstyles.List', {
singleton: true,
/**
* Insert an ordered or unordered list to the cursor position. The list will be removed if already existing and if there is no "css-class" configuration.
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
applyStyle: function (controller)
{
var field = controller.getCurrentField();
field.focus(null, 1);
var tagName = controller.getInitialConfig('tagname');
var node = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), tagName);
if (node == null || !controller.getInitialConfig('css-class')) // when there is no "css-class" we want to remove
{
field.getEditor().execCommand(/ul/i.test(tagName) ? 'InsertUnorderedList' : 'InsertOrderedList');
node = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), tagName);
if (node == null)
{
// may happen when the selection is around the text
// CMS-2483 Problem when creating square list on pasted text
field.getEditor().selection.collapse();
var isThatNode = field.getEditor().selection.getNode().previousSibling;
node = isThatNode && /^ul|ol$/i.test(isThatNode.tagName) ? isThatNode : null;
}
}
if (node != null)
{
var cls = controller.getInitialConfig('css-class') || Ametys.form.widget.RichText.RichTextConfiguration.getTag(tagName, field.getEditor().getParam('category')).attributes['class'].defaultValue;
node.setAttribute("class", cls);
}
field.getEditor().execCommand('mceAddUndoLevel');
},
/**
* Remove the ordered or unordered list
*/
remove: function (controller)
{
var field = controller.getCurrentField();
if (field.getEditor() != null && field.getEditor().dom != null)
{
var n = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName); });
if (n != null)
{
field.getEditor().execCommand(/ul/i.test(n.tagName) ? 'InsertUnorderedList' : 'InsertOrderedList');
}
field.getEditor().focus();
}
},
/**
* Enable/disable and toggle/untoggle controller according the current selection
* @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.
*/
selectionListener: function (controller, field, node)
{
if (field != null && node != null && field.getEditor() != null && field.getEditor().dom != null)
{
controller.enable();
var specificNode = field.getEditor().dom.getParent(node, controller.getInitialConfig('tagname'));
if (specificNode != null)
{
controller.toggle(!controller.getInitialConfig('css-class') || specificNode.className.split(" ").indexOf(controller.getInitialConfig('css-class')) >= 0);
return;
}
}
else
{
controller.disable();
}
controller.toggle(false);
},
/**
* Enable/disable and toggle/untoggle controller according the empty selection
* @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.
*/
noSelectionListener: function (controller, field, node)
{
if (field == null || node == null || field.getEditor() == null || field.getEditor().dom == null)
{
controller.disable();
controller.toggle(false);
}
else
{
var li = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName); });
controller.enable();
controller.toggle(li == null || li.tagName.toLowerCase() != controller.getInitialConfig('tagname').toLowerCase());
}
}
});