MDL-46531 editor_atto: Handle a null selection better on colour change

Sadly, there is no easy way to do this. When a range is empty, we need to
create a new span, make the changes there, and then select it. Rangy does
not provide a helper to do this yet (will be in 1.3) so we have to do it
manually in the mean time.
This commit is contained in:
Andrew Nicols
2014-11-19 14:42:02 +08:00
parent a74d4743e1
commit db97ff0ee6
8 changed files with 194 additions and 38 deletions
@@ -74,14 +74,40 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} backColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'backcolor']);
_changeStyle: function(e, backColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + backColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});
@@ -1 +1 @@
YUI.add("moodle-atto_backcolor-button",function(e,t){var n=[{name:"white"},{name:"red"},{name:"yellow"},{name:"green"},{name:"blue"},{name:"black"}];e.namespace("M.atto_backcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(n,function(e){t.push({text:'<div class="'+e.name+'"></div>',callbackArgs:e.className||e.name})}),this.addToolbarMenu({icon:"e/text_highlight",overlayWidth:"4",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(t,n){var r=e.stamp({});this.get("host").toggleInlineSelectionClass([r]),this.editor.one("."+r).setAttribute("class",""),this.get("host").toggleInlineSelectionClass([n,"backcolor"])}})},"@VERSION@");
YUI.add("moodle-atto_backcolor-button",function(e,t){var n=[{name:"white"},{name:"red"},{name:"yellow"},{name:"green"},{name:"blue"},{name:"black"}];e.namespace("M.atto_backcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(n,function(e){t.push({text:'<div class="'+e.name+'"></div>',callbackArgs:e.className||e.name})}),this.addToolbarMenu({icon:"e/text_highlight",overlayWidth:"4",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(t,n){var r=this.get("host"),i=r.PLACEHOLDER_CLASS,s=r.getSelection(),o=rangy.createCssClassApplier(i,{normalize:!0});o.applyToSelection();if(!e.one("."+i)){var u=s[0],a=u.createContextualFragment('<span class="'+i+'">&nbsp;</span>'),f=rangy.createRange(),l;u.insertNode(a.lastChild),u.collapse(),f.selectNodeContents(e.one("."+i).getDOMNode()),l=rangy.getSelection(),l.setSingleRange(f),s=l.getAllRanges()}this.editor.all("."+i).each(function(e){e.setAttribute("class","fontcolor "+n)},this),r.setSelection(s),this.markUpdated()}})},"@VERSION@");
@@ -74,14 +74,40 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} backColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'backcolor']);
_changeStyle: function(e, backColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + backColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});
@@ -72,13 +72,39 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} backColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'backcolor']);
_changeStyle: function(e, backColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + backColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});
@@ -76,14 +76,40 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} fontColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'fontcolor']);
_changeStyle: function(e, fontColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + fontColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});
@@ -1 +1 @@
YUI.add("moodle-atto_fontcolor-button",function(e,t){var n=[{name:"white"},{name:"red"},{name:"yellow"},{name:"green"},{name:"blue"},{name:"black"}];e.namespace("M.atto_fontcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(n,function(e){t.push({text:'<div class="'+e.name+'"></div>',callbackArgs:e.className||e.name,callback:this._changeStyle})}),this.addToolbarMenu({icon:"e/text_color",overlayWidth:"4",menuColor:"#333333",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(t,n){var r=e.stamp({});this.get("host").toggleInlineSelectionClass([r]),this.editor.one("."+r).setAttribute("class",""),this.get("host").toggleInlineSelectionClass([n,"fontcolor"])}})},"@VERSION@");
YUI.add("moodle-atto_fontcolor-button",function(e,t){var n=[{name:"white"},{name:"red"},{name:"yellow"},{name:"green"},{name:"blue"},{name:"black"}];e.namespace("M.atto_fontcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(n,function(e){t.push({text:'<div class="'+e.name+'"></div>',callbackArgs:e.className||e.name,callback:this._changeStyle})}),this.addToolbarMenu({icon:"e/text_color",overlayWidth:"4",menuColor:"#333333",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(t,n){var r=this.get("host"),i=r.PLACEHOLDER_CLASS,s=r.getSelection(),o=rangy.createCssClassApplier(i,{normalize:!0});o.applyToSelection();if(!e.one("."+i)){var u=s[0],a=u.createContextualFragment('<span class="'+i+'">&nbsp;</span>'),f=rangy.createRange(),l;u.insertNode(a.lastChild),u.collapse(),f.selectNodeContents(e.one("."+i).getDOMNode()),l=rangy.getSelection(),l.setSingleRange(f),s=l.getAllRanges()}this.editor.all("."+i).each(function(e){e.setAttribute("class","fontcolor "+n)},this),r.setSelection(s),this.markUpdated()}})},"@VERSION@");
@@ -76,14 +76,40 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} fontColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'fontcolor']);
_changeStyle: function(e, fontColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + fontColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});
@@ -74,13 +74,39 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} className The class for the new font
* @param {string} fontColorClass The class for the new font
* @private
*/
_changeStyle: function(e, className) {
var id = Y.stamp({});
this.get('host').toggleInlineSelectionClass([id]);
this.editor.one('.' + id).setAttribute('class', '');
this.get('host').toggleInlineSelectionClass([className, 'fontcolor']);
_changeStyle: function(e, fontColorClass) {
var host = this.get('host'),
classname = host.PLACEHOLDER_CLASS,
originalSelection = host.getSelection(),
cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
if (!Y.one('.' + classname)) {
// The selection is likely empty. Create an empty span, and focus it.
var firstRange = originalSelection[0],
fragment = firstRange.createContextualFragment('<span class="' + classname + '">&nbsp;</span>'),
newRange = rangy.createRange(),
selection;
firstRange.insertNode(fragment.lastChild);
firstRange.collapse();
// Update the originalSelection to point to the newly selected range.
newRange.selectNodeContents(Y.one('.' + classname).getDOMNode());
selection = rangy.getSelection();
selection.setSingleRange(newRange);
originalSelection = selection.getAllRanges();
}
this.editor.all('.' + classname).each(function (node) {
node.setAttribute('class', 'fontcolor ' + fontColorClass);
}, this);
host.setSelection(originalSelection);
this.markUpdated();
}
});