webui-aria2/js/libs/dojox/editor/plugins/NormalizeStyle.js.uncompressed.js

560 lines
16 KiB
JavaScript
Raw Normal View History

//>>built
define("dojox/editor/plugins/NormalizeStyle", [
"dojo",
"dijit",
"dojox",
"dijit/_editor/html",
"dijit/_editor/_Plugin",
"dojo/_base/connect",
"dojo/_base/declare"
], function(dojo, dijit, dojox) {
dojo.declare("dojox.editor.plugins.NormalizeStyle",dijit._editor._Plugin,{
// summary:
// This plugin provides NormalizeStyle cabability to the editor. It is
// a headless plugin that tries to normalize how content is styled when
// it comes out of th editor ('b' or css). It also auto-converts
// incoming content to the proper one expected by the browser as well so
// that the native styling buttons work.
// mode [public] String
// A String variable indicating if it should use semantic tags 'b', 'i', etc, or
// CSS styling. The default is semantic.
mode: "semantic",
// condenseSpans [public] Boolean
// A boolean variable indicating if it should try to condense
// 'span''span''span' styles when in css mode
// The default is true, it will try to combine where it can.
condenseSpans: true,
setEditor: function(editor){
// summary:
// Over-ride for the setting of the editor.
// editor: Object
// The editor to configure for this plugin to use.
this.editor = editor;
editor.customUndo = true;
if(this.mode === "semantic"){
this.editor.contentDomPostFilters.push(dojo.hitch(this, this._convertToSemantic));
}else if(this.mode === "css"){
this.editor.contentDomPostFilters.push(dojo.hitch(this, this._convertToCss));
}
// Pre DOM filters are usually based on what browser, as they all use different ways to
// apply styles with actions and modify them.
if(dojo.isIE){
// IE still uses semantic tags most of the time, so convert to that.
this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic));
this._browserFilter = this._convertToSemantic;
}else if(dojo.isWebKit){
this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToCss));
this._browserFilter = this._convertToCss;
}else if(dojo.isMoz){
//Editor currently forces Moz into semantic mode, so we need to match. Ideally
//editor could get rid of that and just use CSS mode, which would work cleaner
//That's why this is split out, to make it easy to change later.
this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic));
this._browserFilter = this._convertToSemantic;
}else{
this.editor.contentDomPreFilters.push(dojo.hitch(this, this._convertToSemantic));
this._browserFilter = this._convertToSemantic;
}
// Set up the inserthtml impl over-ride. This catches semi-paste events and
// tries to normalize them too.
if(this.editor._inserthtmlImpl){
this.editor._oldInsertHtmlImpl = this.editor._inserthtmlImpl;
}
this.editor._inserthtmlImpl = dojo.hitch(this, this._inserthtmlImpl);
},
_convertToSemantic: function(node){
// summary:
// A function to convert the HTML structure of 'node' into
// semantic tags where possible.
// node: DOMNode
// The node to process.
// tags:
// private
if(node){
var w = this.editor.window;
var self = this;
var convertNode = function(cNode){
if(cNode.nodeType == 1){
if(cNode.id !== "dijitEditorBody"){
var style = cNode.style;
var tag = cNode.tagName?cNode.tagName.toLowerCase():"";
var sTag;
if(style && tag != "table" && tag != "ul" && tag != "ol"){
// Avoid wrapper blocks that have specific underlying structure, as injecting
// spans/etc there is invalid.
// Lets check and convert certain node/style types.
var fw = style.fontWeight? style.fontWeight.toLowerCase() : "";
var fs = style.fontStyle? style.fontStyle.toLowerCase() : "";
var td = style.textDecoration? style.textDecoration.toLowerCase() : "";
var s = style.fontSize?style.fontSize.toLowerCase() : "";
var bc = style.backgroundColor?style.backgroundColor.toLowerCase() : "";
var c = style.color?style.color.toLowerCase() : "";
var wrapNodes = function(wrap, pNode){
if(wrap){
while(pNode.firstChild){
wrap.appendChild(pNode.firstChild);
}
if(tag == "span" && !pNode.style.cssText){
// A styler tag with nothing extra in it, so lets remove it.
dojo.place(wrap, pNode, "before");
pNode.parentNode.removeChild(pNode);
pNode = wrap;
}else{
pNode.appendChild(wrap);
}
}
return pNode;
};
switch(fw){
case "bold":
case "bolder":
case "700":
case "800":
case "900":
sTag = dojo.withGlobal(w, "create", dojo, ["b", {}] );
cNode.style.fontWeight = "";
break;
}
cNode = wrapNodes(sTag, cNode);
sTag = null;
if(fs == "italic"){
sTag = dojo.withGlobal(w, "create", dojo, ["i", {}] );
cNode.style.fontStyle = "";
}
cNode = wrapNodes(sTag, cNode);
sTag = null;
if(td){
var da = td.split(" ");
var count = 0;
dojo.forEach(da, function(s){
switch(s){
case "underline":
sTag = dojo.withGlobal(w, "create", dojo, ["u", {}] );
break;
case "line-through":
sTag = dojo.withGlobal(w, "create", dojo, ["strike", {}] );
break;
}
count++;
if(count == da.length){
// Last one, clear the decor and see if we can span strip on wrap.
cNode.style.textDecoration = "";
}
cNode = wrapNodes(sTag, cNode);
sTag = null;
});
}
if(s){
var sizeMap = {
"xx-small": 1,
"x-small": 2,
"small": 3,
"medium": 4,
"large": 5,
"x-large": 6,
"xx-large": 7,
"-webkit-xxx-large": 7
};
// Convert point or px size to size
// to something roughly mappable.
if(s.indexOf("pt") > 0){
s = s.substring(0,s.indexOf("pt"));
s = parseInt(s);
if(s < 5){
s = "xx-small";
}else if(s < 10){
s = "x-small";
}else if(s < 15){
s = "small";
}else if(s < 20){
s = "medium";
}else if(s < 25){
s = "large";
}else if(s < 30){
s = "x-large";
}else if(s > 30){
s = "xx-large";
}
}else if(s.indexOf("px") > 0){
s = s.substring(0,s.indexOf("px"));
s = parseInt(s);
if(s < 5){
s = "xx-small";
}else if(s < 10){
s = "x-small";
}else if(s < 15){
s = "small";
}else if(s < 20){
s = "medium";
}else if(s < 25){
s = "large";
}else if(s < 30){
s = "x-large";
}else if(s > 30){
s = "xx-large";
}
}
var size = sizeMap[s];
if(!size){
size = 3;
}
sTag = dojo.withGlobal(w, "create", dojo, ["font", {size: size}] );
cNode.style.fontSize = "";
}
cNode = wrapNodes(sTag, cNode);
sTag = null;
if(bc && tag !== "font" && self._isInline(tag)){
// IE doesn't like non-font background color crud.
// Also, don't move it in if the background color is set on a block style node,
// as it won't color properly once put on inline font.
bc = new dojo.Color(bc).toHex();
sTag = dojo.withGlobal(w, "create", dojo, ["font", {style: {backgroundColor: bc}}] );
cNode.style.backgroundColor = "";
}
if(c && tag !== "font"){
// IE doesn't like non-font background color crud.
c = new dojo.Color(c).toHex();
sTag = dojo.withGlobal(w, "create", dojo, ["font", {color: c}] );
cNode.style.color = "";
}
cNode = wrapNodes(sTag, cNode);
sTag = null;
}
}
if(cNode.childNodes){
// Clone it, since we may alter its position
var nodes = [];
dojo.forEach(cNode.childNodes, function(n){ nodes.push(n);});
dojo.forEach(nodes, convertNode);
}
}
return cNode;
};
return this._normalizeTags(convertNode(node));
}
return node;
},
_normalizeTags: function(node){
// summary:
// A function to handle normalizing certain tag types contained under 'node'
// node:
// The node to search from.
// tags:
// Protected.
var w = this.editor.window;
var nodes = dojo.withGlobal(w, function() {
return dojo.query("em,s,strong", node);
});
if(nodes && nodes.length){
dojo.forEach(nodes, function(n){
if(n){
var tag = n.tagName?n.tagName.toLowerCase():"";
var tTag;
switch(tag){
case "s":
tTag = "strike";
break;
case "em":
tTag = "i";
break;
case "strong":
tTag = "b";
break;
}
if(tTag){
var nNode = dojo.withGlobal(w, "create", dojo, [tTag, null, n, "before"] );
while(n.firstChild){
nNode.appendChild(n.firstChild);
}
n.parentNode.removeChild(n);
}
}
});
}
return node;
},
_convertToCss: function(node){
// summary:
// A function to convert the HTML structure of 'node' into
// css span styles around text instead of semantic tags.
// Note: It does not do compression of spans together.
// node: DOMNode
// The node to process
// tags:
// private
if(node){
var w = this.editor.window;
var convertNode = function(cNode) {
if(cNode.nodeType == 1){
if(cNode.id !== "dijitEditorBody"){
var tag = cNode.tagName?cNode.tagName.toLowerCase():"";
if(tag){
var span;
switch(tag){
case "b":
case "strong": // Mainly IE
span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"fontWeight": "bold"}}] );
break;
case "i":
case "em": // Mainly IE
span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"fontStyle": "italic"}}] );
break;
case "u":
span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"textDecoration": "underline"}}] );
break;
case "strike":
case "s": // Mainly WebKit.
span = dojo.withGlobal(w, "create", dojo, ["span", {style: {"textDecoration": "line-through"}}] );
break;
case "font": // Try to deal with colors
var styles = {};
if(dojo.attr(cNode, "color")){
styles.color = dojo.attr(cNode, "color");
}
if(dojo.attr(cNode, "face")){
styles.fontFace = dojo.attr(cNode, "face");
}
if(cNode.style && cNode.style.backgroundColor){
styles.backgroundColor = cNode.style.backgroundColor;
}
if(cNode.style && cNode.style.color){
styles.color = cNode.style.color;
}
var sizeMap = {
1: "xx-small",
2: "x-small",
3: "small",
4: "medium",
5: "large",
6: "x-large",
7: "xx-large"
};
if(dojo.attr(cNode, "size")){
styles.fontSize = sizeMap[dojo.attr(cNode, "size")];
}
span = dojo.withGlobal(w, "create", dojo, ["span", {style: styles}] );
break;
}
if(span){
while(cNode.firstChild){
span.appendChild(cNode.firstChild);
}
dojo.place(span, cNode, "before");
cNode.parentNode.removeChild(cNode);
cNode = span;
}
}
}
if(cNode.childNodes){
// Clone it, since we may alter its position
var nodes = [];
dojo.forEach(cNode.childNodes, function(n){ nodes.push(n);});
dojo.forEach(nodes, convertNode);
}
}
return cNode;
};
node = convertNode(node);
if(this.condenseSpans){
this._condenseSpans(node);
}
}
return node;
},
_condenseSpans: function(node){
// summary:
// Method to condense spans if you end up with multi-wrapping from
// from converting b, i, u, to span nodes.
// node:
// The node (and its children), to process.
// tags:
// private
var compressSpans = function(node){
// Okay, span with no class or id and it has styles.
// So, merge the styles, then collapse. Merge requires determining
// all the common/different styles and anything that overlaps the style,
// but a different value can't be merged.
var genStyleMap = function(styleText){
var m;
if(styleText){
m = {};
var styles = styleText.toLowerCase().split(";");
dojo.forEach(styles, function(s){
if(s){
var ss = s.split(":");
var key = ss[0] ? dojo.trim(ss[0]): "";
var val = ss[1] ? dojo.trim(ss[1]): "";
if(key && val){
var i;
var nKey = "";
for(i = 0; i < key.length; i++){
var ch = key.charAt(i);
if(ch == "-"){
i++;
ch = key.charAt(i);
nKey += ch.toUpperCase();
}else{
nKey += ch;
}
}
m[nKey] = val;
}
}
});
}
return m;
};
if(node && node.nodeType == 1){
var tag = node.tagName? node.tagName.toLowerCase() : "";
if(tag === "span" && node.childNodes && node.childNodes.length === 1){
// Okay, a possibly compressible span
var c = node.firstChild;
while(c && c.nodeType == 1 && c.tagName && c.tagName.toLowerCase() == "span"){
if(!dojo.attr(c, "class") && !dojo.attr(c, "id") && c.style){
var s1 = genStyleMap(node.style.cssText);
var s2 = genStyleMap(c.style.cssText);
if(s1 && s2){
// Maps, so lets see if we can combine them.
var combinedMap = {};
var i;
for(i in s1){
if(!s1[i] || !s2[i] || s1[i] == s2[i]){
combinedMap[i] = s1[i];
delete s2[i];
}else if(s1[i] != s2[i]){
// Collision, cannot merge.
// IE does not handle combined underline strikethrough text
// decorations on a single span.
if(i == "textDecoration"){
combinedMap[i] = s1[i] + " " + s2[i];
delete s2[i];
}else{
combinedMap = null;
}
break;
}else{
combinedMap = null;
break;
}
}
if(combinedMap){
for(i in s2){
combinedMap[i] = s2[i];
}
dojo.style(node, combinedMap);
while(c.firstChild){
node.appendChild(c.firstChild);
}
var t = c.nextSibling;
c.parentNode.removeChild(c);
c = t;
}else{
c = c.nextSibling;
}
}else{
c = c.nextSibling;
}
}else{
c = c.nextSibling;
}
}
}
}
if(node.childNodes && node.childNodes.length){
dojo.forEach(node.childNodes, compressSpans);
}
};
compressSpans(node);
},
_isInline: function(tag){
// summary:
// Function to determine if the current tag is an inline
// element that does formatting, as we don't want to
// try to combine inlines with divs on styles.
// tag:
// The tag to examine
// tags:
// private
switch(tag){
case "a":
case "b":
case "strong":
case "s":
case "strike":
case "i":
case "u":
case "em":
case "sup":
case "sub":
case "span":
case "font":
case "big":
case "cite":
case "q":
case "img":
case "small":
return true;
default:
return false;
}
},
_inserthtmlImpl: function(html){
// summary:
// Function to trap and over-ride the editor inserthtml implementation
// to try and filter it to match the editor's internal styling mode.
// Helpful for plugins like PasteFromWord, in that it extra-filters
// and normalizes the input if it can.
// html:
// The HTML string to insert.
// tags:
// private
if(html){
var div = this.editor.document.createElement("div");
div.innerHTML = html;
div = this._browserFilter(div);
html = dijit._editor.getChildrenHtml(div);
div.innerHTML = "";
// Call the over-ride, or if not available, just execute it.
if(this.editor._oldInsertHtmlImpl){
return this.editor._oldInsertHtmlImpl(html);
}else{
return this.editor.execCommand("inserthtml", html);
}
}
return false;
}
});
// Register this plugin.
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
if(o.plugin){ return; }
var name = o.args.name.toLowerCase();
if(name === "normalizestyle"){
o.plugin = new dojox.editor.plugins.NormalizeStyle({
mode: ("mode" in o.args)?o.args.mode:"semantic",
condenseSpans: ("condenseSpans" in o.args)?o.args.condenseSpans:true
});
}
});
return dojox.editor.plugins.NormalizeStyle;
});