560 lines
16 KiB
JavaScript
560 lines
16 KiB
JavaScript
//>>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;
|
|
|
|
});
|