Merge branch 'MDL-44846-master' of git://github.com/damyon/moodle

This commit is contained in:
Sam Hemelryk 2014-04-07 08:48:50 +12:00
commit 2429e44011
14 changed files with 382 additions and 580 deletions

View File

@ -33,9 +33,7 @@ YUI.add('moodle-atto_backcolor-button', function (Y, NAME) {
* @extends M.editor_atto.EditorPlugin
*/
var doc = document,
BackColor = 'BackColor',
colors = [
var colors = [
{
name: 'white',
color: '#FFFFFF'
@ -88,54 +86,9 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
this._fallbackChangeStyle(color);
}
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
doc.designMode = "off";
return this;
this.get('host').formatSelectionInlineStyle({
backgroundColor: color
});
}
});

View File

@ -1 +1 @@
YUI.add("moodle-atto_backcolor-button",function(e,t){var n=document,r="BackColor",i=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];e.namespace("M.atto_backcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(i,function(e){t.push({text:'<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color})}),this.addToolbarMenu({icon:"e/text_highlight",overlayWidth:"4",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){if(window.getSelection)try{n.execCommand(r,!1,t)||this._fallbackChangeStyle(t)}catch(i){this._fallbackChangeStyle(t)}else n.selection&&n.selection.createRange&&(range=n.selection.createRange(),range.execCommand(r,!1,t));this.markUpdated()},_fallbackChangeStyle:function(e){var t=window.getSelection(),i;return t.rangeCount&&t.getRangeAt&&(i=t.getRangeAt(0)),n.designMode="on",i&&(t.removeAllRanges(),t.addRange(i)),n.execCommand("HiliteColor",!1,e)||n.execCommand(r,!1,e),n.designMode="off",this}})},"@VERSION@");
YUI.add("moodle-atto_backcolor-button",function(e,t){var n=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];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 style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color})}),this.addToolbarMenu({icon:"e/text_highlight",overlayWidth:"4",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){this.get("host").formatSelectionInlineStyle({backgroundColor:t})}})},"@VERSION@");

View File

@ -33,9 +33,7 @@ YUI.add('moodle-atto_backcolor-button', function (Y, NAME) {
* @extends M.editor_atto.EditorPlugin
*/
var doc = document,
BackColor = 'BackColor',
colors = [
var colors = [
{
name: 'white',
color: '#FFFFFF'
@ -88,54 +86,9 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
this._fallbackChangeStyle(color);
}
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
doc.designMode = "off";
return this;
this.get('host').formatSelectionInlineStyle({
backgroundColor: color
});
}
});

View File

@ -31,9 +31,7 @@
* @extends M.editor_atto.EditorPlugin
*/
var doc = document,
BackColor = 'BackColor',
colors = [
var colors = [
{
name: 'white',
color: '#FFFFFF'
@ -86,53 +84,8 @@ Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
this._fallbackChangeStyle(color);
}
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
doc.designMode = "off";
return this;
this.get('host').formatSelectionInlineStyle({
backgroundColor: color
});
}
});

View File

@ -88,10 +88,9 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
this.get('host').formatSelectionInlineStyle({
color: color
});
}
});

View File

@ -1 +1 @@
YUI.add("moodle-atto_fontcolor-button",function(e,t){var n=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];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 style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color,callback:this._changeStyle})}),this.addToolbarMenu({icon:"e/text_color",overlayWidth:"4",menuColor:"#333333",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){document.execCommand("forecolor",0,t),this.markUpdated()}})},"@VERSION@");
YUI.add("moodle-atto_fontcolor-button",function(e,t){var n=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];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 style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color,callback:this._changeStyle})}),this.addToolbarMenu({icon:"e/text_color",overlayWidth:"4",menuColor:"#333333",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){this.get("host").formatSelectionInlineStyle({color:t})}})},"@VERSION@");

View File

@ -88,10 +88,9 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
this.get('host').formatSelectionInlineStyle({
color: color
});
}
});

View File

@ -86,9 +86,8 @@ Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto
* @private
*/
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
this.get('host').formatSelectionInlineStyle({
color: color
});
}
});

View File

@ -103,7 +103,7 @@ Y.extend(Editor, Y.Base, {
'video'
],
PLACEHOLDER_FONTNAME: 'yui-tmp',
PLACEHOLDER_CLASS: 'atto-tmp-class',
ALL_NODES_SELECTOR: '[style],font[face]',
FONT_FAMILY: 'fontFamily',
@ -1171,107 +1171,6 @@ EditorSelection.prototype = {
selection.setRanges(ranges);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return false;
}
boundary = this.editor;
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
},
/**
* Inserts the given HTML into the editable content at the currently focused point.
*
@ -1382,55 +1281,138 @@ EditorStyling.prototype = {
* @param {Array} toggleclasses - Class names to be toggled on or off.
*/
toggleInlineSelectionClass: function(toggleclasses) {
var classname = toggleclasses.join(" ");
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.toggleSelection();
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* This will set inline styles on the current selection.
*
* @method toggleInlineSelectionClass
* @param {Array} styles - Style attributes to set on the nodes.
*/
formatSelectionInlineStyle: function(styles) {
var classname = this.PLACEHOLDER_CLASS;
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
this.editor.all('.' + classname).each(function (node) {
node.removeClass(classname).setStyles(styles);
}, this);
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
nodes,
items = [],
parentspan,
currentnode,
newnode,
i = 0;
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return;
return false;
}
// Add a bogus fontname as the browsers handle inserting fonts into multiple blocks correctly.
document.execCommand('fontname', false, this.PLACEHOLDER_FONTNAME);
nodes = this.editor.all(this.ALL_NODES_SELECTOR);
boundary = this.editor;
// Create a list of all nodes that have our bogus fontname.
nodes.each(function(node, index) {
if (node.getStyle(this.FONT_FAMILY) === this.PLACEHOLDER_FONTNAME) {
node.setStyle(this.FONT_FAMILY, '');
if (!node.compareTo(this.editor)) {
items.push(Y.Node.getDOMNode(nodes.item(index)));
}
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
});
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
// Replace the fontname tags with spans
for (i = 0; i < items.length; i++) {
currentnode = Y.one(items[i]);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
// Check for an existing span to add the nolink class to.
parentspan = currentnode.ancestor('.editor_atto_content span');
if (!parentspan) {
newnode = Y.Node.create('<span></span>');
newnode.append(items[i].innerHTML);
currentnode.replace(newnode);
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
currentnode = newnode;
} else {
currentnode = parentspan;
}
// Toggle the classes on the spans.
for (var j = 0; j < toggleclasses.length; j++) {
currentnode.toggleClass(toggleclasses[j]);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
}
};
Y.Base.mix(Y.M.editor_atto.Editor, [EditorStyling]);

File diff suppressed because one or more lines are too long

View File

@ -103,7 +103,7 @@ Y.extend(Editor, Y.Base, {
'video'
],
PLACEHOLDER_FONTNAME: 'yui-tmp',
PLACEHOLDER_CLASS: 'atto-tmp-class',
ALL_NODES_SELECTOR: '[style],font[face]',
FONT_FAMILY: 'fontFamily',
@ -1167,107 +1167,6 @@ EditorSelection.prototype = {
selection.setRanges(ranges);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return false;
}
boundary = this.editor;
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
},
/**
* Inserts the given HTML into the editable content at the currently focused point.
*
@ -1378,55 +1277,138 @@ EditorStyling.prototype = {
* @param {Array} toggleclasses - Class names to be toggled on or off.
*/
toggleInlineSelectionClass: function(toggleclasses) {
var classname = toggleclasses.join(" ");
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.toggleSelection();
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* This will set inline styles on the current selection.
*
* @method toggleInlineSelectionClass
* @param {Array} styles - Style attributes to set on the nodes.
*/
formatSelectionInlineStyle: function(styles) {
var classname = this.PLACEHOLDER_CLASS;
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
this.editor.all('.' + classname).each(function (node) {
node.removeClass(classname).setStyles(styles);
}, this);
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
nodes,
items = [],
parentspan,
currentnode,
newnode,
i = 0;
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return;
return false;
}
// Add a bogus fontname as the browsers handle inserting fonts into multiple blocks correctly.
document.execCommand('fontname', false, this.PLACEHOLDER_FONTNAME);
nodes = this.editor.all(this.ALL_NODES_SELECTOR);
boundary = this.editor;
// Create a list of all nodes that have our bogus fontname.
nodes.each(function(node, index) {
if (node.getStyle(this.FONT_FAMILY) === this.PLACEHOLDER_FONTNAME) {
node.setStyle(this.FONT_FAMILY, '');
if (!node.compareTo(this.editor)) {
items.push(Y.Node.getDOMNode(nodes.item(index)));
}
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
});
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
// Replace the fontname tags with spans
for (i = 0; i < items.length; i++) {
currentnode = Y.one(items[i]);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
// Check for an existing span to add the nolink class to.
parentspan = currentnode.ancestor('.editor_atto_content span');
if (!parentspan) {
newnode = Y.Node.create('<span></span>');
newnode.append(items[i].innerHTML);
currentnode.replace(newnode);
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
currentnode = newnode;
} else {
currentnode = parentspan;
}
// Toggle the classes on the spans.
for (var j = 0; j < toggleclasses.length; j++) {
currentnode.toggleClass(toggleclasses[j]);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
}
};
Y.Base.mix(Y.M.editor_atto.Editor, [EditorStyling]);

View File

@ -101,7 +101,7 @@ Y.extend(Editor, Y.Base, {
'video'
],
PLACEHOLDER_FONTNAME: 'yui-tmp',
PLACEHOLDER_CLASS: 'atto-tmp-class',
ALL_NODES_SELECTOR: '[style],font[face]',
FONT_FAMILY: 'fontFamily',

View File

@ -342,107 +342,6 @@ EditorSelection.prototype = {
selection.setRanges(ranges);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return false;
}
boundary = this.editor;
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
},
/**
* Inserts the given HTML into the editable content at the currently focused point.
*

View File

@ -86,55 +86,138 @@ EditorStyling.prototype = {
* @param {Array} toggleclasses - Class names to be toggled on or off.
*/
toggleInlineSelectionClass: function(toggleclasses) {
var classname = toggleclasses.join(" ");
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.toggleSelection();
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* This will set inline styles on the current selection.
*
* @method toggleInlineSelectionClass
* @param {Array} styles - Style attributes to set on the nodes.
*/
formatSelectionInlineStyle: function(styles) {
var classname = this.PLACEHOLDER_CLASS;
var originalSelection = this.getSelection();
var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
cssApplier.applyToSelection();
this.editor.all('.' + classname).each(function (node) {
node.removeClass(classname).setStyles(styles);
}, this);
this.setSelection(originalSelection);
},
/**
* Change the formatting for the current selection.
*
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @method formatSelectionBlock
* @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
* @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
*/
formatSelectionBlock: function(blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = this.getSelectionParentNode(),
nodes,
items = [],
parentspan,
currentnode,
newnode,
i = 0;
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;
if (!selectionparentnode) {
// No selection, nothing to format.
return;
return false;
}
// Add a bogus fontname as the browsers handle inserting fonts into multiple blocks correctly.
document.execCommand('fontname', false, this.PLACEHOLDER_FONTNAME);
nodes = this.editor.all(this.ALL_NODES_SELECTOR);
boundary = this.editor;
// Create a list of all nodes that have our bogus fontname.
nodes.each(function(node, index) {
if (node.getStyle(this.FONT_FAMILY) === this.PLACEHOLDER_FONTNAME) {
node.setStyle(this.FONT_FAMILY, '');
if (!node.compareTo(this.editor)) {
items.push(Y.Node.getDOMNode(nodes.item(index)));
}
selectionparentnode = Y.one(selectionparentnode);
// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
});
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);
// Replace the fontname tags with spans
for (i = 0; i < items.length; i++) {
currentnode = Y.one(items[i]);
if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}
// Check for an existing span to add the nolink class to.
parentspan = currentnode.ancestor('.editor_atto_content span');
if (!parentspan) {
newnode = Y.Node.create('<span></span>');
newnode.append(items[i].innerHTML);
currentnode.replace(newnode);
nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);
currentnode = newnode;
} else {
currentnode = parentspan;
}
// Toggle the classes on the spans.
for (var j = 0; j < toggleclasses.length; j++) {
currentnode.toggleClass(toggleclasses[j]);
if (!match) {
nearestblock = false;
}
}
// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}
// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});
nearestblock.replace(replacement);
nearestblock = replacement;
}
// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}
// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = this.getSelectionFromNode(nearestblock);
this.setSelection(selection);
return nearestblock;
}
};
Y.Base.mix(Y.M.editor_atto.Editor, [EditorStyling]);