mirror of
https://github.com/moodle/moodle.git
synced 2025-04-20 07:56:06 +02:00
Merge branch 'MDL-44846-master' of git://github.com/damyon/moodle
This commit is contained in:
commit
2429e44011
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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@");
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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@");
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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
@ -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]);
|
||||
|
2
lib/editor/atto/yui/src/editor/js/editor.js
vendored
2
lib/editor/atto/yui/src/editor/js/editor.js
vendored
@ -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',
|
||||
|
||||
|
101
lib/editor/atto/yui/src/editor/js/selection.js
vendored
101
lib/editor/atto/yui/src/editor/js/selection.js
vendored
@ -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.
|
||||
*
|
||||
|
153
lib/editor/atto/yui/src/editor/js/styling.js
vendored
153
lib/editor/atto/yui/src/editor/js/styling.js
vendored
@ -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]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user